Phương pháp tốt nhất để xử lý tiền tệ / tiền là gì?


323

Tôi đang làm việc trên một hệ thống giỏ hàng rất cơ bản.

Tôi có một bảng itemscó một cột priceloại integer.

Tôi gặp sự cố khi hiển thị giá trị giá theo quan điểm của mình đối với giá bao gồm cả Euro và xu. Tôi có thiếu điều gì rõ ràng khi xử lý tiền tệ trong khung Rails không?


nếu sử dụng một ai đó SQL, sau đó DECIMAL(19, 4) là một lựa chọn phổ biến kiểm tra này cũng kiểm tra ở đây Formats tệ thế giới để quyết định có bao nhiêu nơi để sử dụng số thập phân, hy vọng giúp.
shaijut

Câu trả lời:


495

Bạn có thể muốn sử dụng một DECIMALloại trong cơ sở dữ liệu của bạn. Trong quá trình di chuyển của bạn, hãy làm một cái gì đó như thế này:

# 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

Trong Rails, :decimalloại được trả về là BigDecimal, rất phù hợp để tính giá.

Nếu bạn khăng khăng sử dụng số nguyên, bạn sẽ phải tự chuyển đổi sang và từ BigDecimalmọi nơi, điều này có thể sẽ trở thành một nỗi đau.

Như được chỉ ra bởi mcl, để in giá, sử dụng:

number_to_currency(price, :unit => "€")
#=> €1,234.01

13
Sử dụng trình trợ giúp number_to_currency, thông tin thêm tại api.rubyonrails.org/groupes/ActionView/Helpers/ Kẻ
mlibby

48
Trên thực tế, việc sử dụng một số nguyên kết hợp với act_as_dollars sẽ an toàn và dễ dàng hơn nhiều. Bạn đã bao giờ bị cắn bởi so sánh dấu phẩy động? Nếu không, đừng biến điều này thành trải nghiệm đầu tiên của bạn. :) Với act_as_dollars, bạn đặt công cụ ở định dạng 12.34, nó được lưu trữ là 1234 và xuất hiện dưới dạng 12.34.
Sarah Mei

50
@Sarah Mei: BigDecimals + định dạng cột thập phân tránh chính xác điều đó.
molf

114
Điều quan trọng là không chỉ sao chép câu trả lời này một cách mù quáng - độ chính xác 8, tỷ lệ 2 mang lại cho bạn giá trị tối đa 999.999,99 . Nếu bạn cần một con số lớn hơn một triệu thì hãy tăng độ chính xác!
Jon Cairns

22
Điều quan trọng nữa là không chỉ sử dụng một cách mù quáng thang đo 2 nếu bạn đang xử lý các loại tiền tệ khác nhau - một số loại tiền Bắc Phi và Ả Rập như Rial của Oman hoặc Dinar Tunisia có thang đo 3, vì vậy độ chính xác 8 tỷ lệ 3 phù hợp hơn ở đó .
Đánh bại Richartz

117

Đây là một cách tiếp cận đơn giản, tốt, tận dụng composed_of(một phần của ActiveRecord, sử dụng mẫu ValueObject) và đá quý Money

Có thể bạn sẽ cần

  • Viên ngọc tiền (phiên bản 4.1.0)
  • Một mô hình, ví dụ Product
  • Một integercột trong mô hình của bạn (và cơ sở dữ liệu), ví dụ::price

Viết điều này trong product.rbtập tin của bạn :

class Product > ActiveRecord::Base

  composed_of :price,
              :class_name => 'Money',
              :mapping => %w(price cents),
              :converter => Proc.new { |value| Money.new(value) }
  # ...

Bạn sẽ nhận được gì:

  • Không có bất kỳ thay đổi bổ sung nào, tất cả các biểu mẫu của bạn sẽ hiển thị đô la và xu, nhưng đại diện nội bộ vẫn chỉ là xu. Các biểu mẫu sẽ chấp nhận các giá trị như "$ 12,034,95" và chuyển đổi nó cho bạn. Không cần thêm trình xử lý hoặc thuộc tính bổ sung vào mô hình hoặc trình trợ giúp trong chế độ xem của bạn.
  • product.price = "$12.00" tự động chuyển đổi sang lớp Tiền
  • product.price.to_s hiển thị số được định dạng thập phân ("1234.00")
  • product.price.format hiển thị một chuỗi được định dạng đúng cho tiền tệ
  • Nếu bạn cần gửi xu (đến cổng thanh toán muốn có xu), product.price.cents.to_s
  • Chuyển đổi tiền tệ miễn phí

14
Tôi thích cách tiếp cận này. Nhưng xin lưu ý: đảm bảo di chuyển của bạn cho 'giá' trong ví dụ này không cho phép null và mặc định là 0 vì bạn sẽ phát điên khi cố gắng tìm hiểu tại sao điều này không hiệu quả.
Cory

3
Tôi thấy đá quý money_column (được trích xuất từ ​​Shopify) rất dễ sử dụng ... dễ dàng hơn đá quý tiền, nếu bạn không cần chuyển đổi tiền tệ.
Talyric

7
Cần lưu ý cho tất cả những người sử dụng đá quý Money mà nhóm nòng cốt Rails đang thảo luận về việc phản đối và loại bỏ "comp_of" khỏi khung. Tôi nghi ngờ viên ngọc sẽ được cập nhật để xử lý việc này nếu nó xảy ra, nhưng nếu bạn đang xem Rails 4.0, bạn nên biết về khả năng này
Peer Allan

1
Về nhận xét của @ PeerAllan về việc loại bỏ composed_of ở đây chi tiết hơn về điều đó cũng như việc thực hiện thay thế.
HerbCSO

3
Ngoài ra, điều này thực sự khó khăn khi sử dụng đá quý rails-money .
fotanus

25

Thực tiễn phổ biến để xử lý tiền tệ là sử dụng loại thập phân. Đây là một ví dụ đơn giản từ "Phát triển web nhanh với đường ray"

add_column :products, :price, :decimal, :precision => 8, :scale => 2 

Điều này sẽ cho phép bạn xử lý giá từ -999.999,99 đến 999.999,99
Bạn cũng có thể muốn bao gồm xác nhận trong các mục của mình như

def validate 
  errors.add(:price, "should be at least 0.01") if price.nil? || price < 0.01 
end 

để sanity kiểm tra giá trị của bạn.


1
Giải pháp này cũng cho phép bạn sử dụng SQL sum và bạn bè.
Larry K

4
Bạn có thể làm gì: xác thực: giá ,: hiện diện => đúng ,: số = = {: lớn_than => 0}
Galaxy

9

Nếu bạn đang sử dụng Postgres (và kể từ khi chúng tôi vào năm 2017), bạn có thể muốn :moneythử loại cột của họ .

add_column :products, :price, :money, default: 0

7

Sử dụng đá quý tiền rails . Nó xử lý độc đáo tiền và tiền tệ trong mô hình của bạn và cũng có một loạt các trợ giúp để định dạng giá của bạn.


Vâng, tôi đồng ý với điều này. Nói chung, tôi xử lý tiền bằng cách lưu trữ dưới dạng xu (số nguyên) và sử dụng đá quý như hành vi như tiền hoặc tiền (đường ray tiền) để xử lý dữ liệu trong bộ nhớ. Xử lý nó trong số nguyên ngăn ngừa những lỗi làm tròn khó chịu. Ví dụ: 0,2 * 3 => 0,6000000000000001 Điều này, tất nhiên, chỉ hoạt động nếu bạn không cần xử lý các phân số của một xu.
Chad M

Điều này là rất tốt đẹp nếu bạn đang sử dụng đường ray. Thả nó vào và đừng lo lắng về các vấn đề với cột thập phân. Nếu bạn sử dụng điều này với chế độ xem, câu trả lời này cũng có thể hữu ích: stackoverflow.com/questions/18898947/
mẹo

6

Chỉ cần một bản cập nhật nhỏ và sự gắn kết của tất cả các câu trả lời cho một số đàn em / người mới bắt đầu tham vọng phát triển RoR chắc chắn sẽ đến đây để giải thích.

Làm việc với tiền

Sử dụng :decimalđể lưu trữ tiền trong DB, như @molf đề xuất (và những gì công ty tôi sử dụng làm tiêu chuẩn vàng khi làm việc với tiền).

# 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

Vài điểm:

  • :decimalsẽ được sử dụng BigDecimalđể giải quyết rất nhiều vấn đề.

  • precisionscalenên được điều chỉnh, tùy thuộc vào những gì bạn đang đại diện

    • Nếu bạn làm việc với việc nhận và gửi thanh toán, precision: 8scale: 2cung cấp cho bạn 999,999.99số tiền cao nhất, sẽ ổn trong 90% trường hợp.

    • Nếu bạn cần đại diện cho giá trị của một tài sản hoặc một chiếc xe hiếm, bạn nên sử dụng cao hơn precision.

    • Nếu bạn làm việc với tọa độ (kinh độ và vĩ độ), bạn chắc chắn sẽ cần cao hơn scale.

Cách tạo di chuyển

Để tạo di chuyển với nội dung trên, hãy chạy trong thiết bị đầu cuối:

bin/rails g migration AddPriceToItems price:decimal{8-2}

hoặc là

bin/rails g migration AddPriceToItems 'price:decimal{5,2}'

như được giải thích trong bài viết trên blog này .

Định dạng tiền tệ

HỎI các thư viện bổ sung tạm biệt và sử dụng các trợ giúp tích hợp. Sử dụng number_to_currencynhư @molf và @facundofarias đề xuất.

Để chơi với number_to_currencyhelper trong Rails console, gửi một cuộc gọi đến ActiveSupportcủa NumberHelperlớp để truy cập vào các helper.

Ví dụ:

ActiveSupport::NumberHelper.number_to_currency(2_500_000.61, unit: '€', precision: 2, separator: ',', delimiter: '', format: "%n%u")

đưa ra đầu ra sau

2500000,61

Kiểm tra cái khác optionscủa trình trợ giúp number_to_currency .

Đặt nó ở đâu

Bạn có thể đặt nó trong một trình trợ giúp ứng dụng và sử dụng nó trong các khung nhìn với bất kỳ số tiền nào.

module ApplicationHelper    
  def format_currency(amount)
    number_to_currency(amount, unit: '€', precision: 2, separator: ',', delimiter: '', format: "%n%u")
  end
end

Hoặc bạn có thể đặt nó trong Itemmô hình như một phương thức ví dụ và gọi nó ở nơi bạn cần định dạng giá (trong chế độ xem hoặc người trợ giúp).

class Item < ActiveRecord::Base
  def format_price
    number_to_currency(price, unit: '€', precision: 2, separator: ',', delimiter: '', format: "%n%u")
  end
end

Và, một ví dụ về cách tôi sử dụng number_to_currencybên trong một công cụ kiểm soát (chú ý negative_formattùy chọn, được sử dụng để thể hiện khoản hoàn trả)

def refund_information
  amount_formatted = 
    ActionController::Base.helpers.number_to_currency(@refund.amount, negative_format: '(%u%n)')
  {
    # ...
    amount_formatted: amount_formatted,
    # ...
  }
end

5

Sử dụng các thuộc tính ảo (Liên kết đến Railscast đã sửa đổi (phải trả tiền), bạn có thể lưu trữ price_in_cents của mình trong một cột số nguyên và thêm một thuộc tính ảo price_in_dollars trong mô hình sản phẩm của bạn dưới dạng getter và 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

Nguồn: RailsCasts # 016: Thuộc tính ảo : thuộc tính ảo là một cách sạch để thêm các trường hình thức mà không lập bản đồ trực tiếp đến cơ sở dữ liệu. Ở đây tôi chỉ ra cách xử lý các xác nhận, các hiệp hội, và nhiều hơn nữa.


1
Điều này để lại 200,0 một chữ số
ajbraus

2

Chắc chắn nguyên .

Và mặc dù BigDecimal tồn tại về mặt kỹ thuật 1.5vẫn sẽ cung cấp cho bạn một Float thuần túy trong Ruby.


2

Nếu ai đó đang sử dụng Sequel, việc di chuyển sẽ trông giống như:

add_column :products, :price, "decimal(8,2)"

bằng cách nào đó Sequel bỏ qua: độ chính xác và: tỷ lệ

(Phiên bản tiếp theo: phần tiếp theo (3.39.0, 3.38.0))


2

Các API cơ bản của tôi đều sử dụng xu để thể hiện tiền và tôi không muốn thay đổi điều đó. Tôi cũng không làm việc với số tiền lớn. Vì vậy, tôi chỉ đặt điều này trong một phương thức trợ giúp:

sprintf("%03d", amount).insert(-3, ".")

Điều đó chuyển đổi số nguyên thành một chuỗi có ít nhất ba chữ số (thêm các số 0 đứng đầu nếu cần), sau đó chèn một dấu thập phân trước hai chữ số cuối, không bao giờ sử dụng a Float. Từ đó bạn có thể thêm bất kỳ ký hiệu tiền tệ nào phù hợp với trường hợp sử dụng của bạn.

chắc chắn nhanh và bẩn, nhưng đôi khi điều đó thật tốt!


Không thể tin rằng không ai ủng hộ bạn. Đây là điều duy nhất hoạt động để đưa đối tượng Tiền của tôi trở thành một hình thức sao cho API có thể lấy nó. (Số thập phân)
Code-MonKy

2

Tôi đang sử dụng nó theo cách này:

number_to_currency(amount, unit: '€', precision: 2, format: "%u %n")

Tất nhiên là ký hiệu tiền tệ, độ chính xác, định dạng và tùy thuộc vào từng loại tiền tệ.


1

Bạn có thể chuyển một số tùy chọn cho number_to_currency(trình trợ giúp chế độ xem Rails 4 tiêu chuẩn):

number_to_currency(12.0, :precision => 2)
# => "$12.00"

Như được đăng bởi Dylan Markow


0

Mã đơn giản cho Ruby & Rails

<%= number_to_currency(1234567890.50) %>

OUT PUT => $1,234,567,890.50
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.