Apa metode terbaik untuk menangani mata uang / uang?

323

Saya sedang mengerjakan sistem kereta belanja yang sangat mendasar.

Saya punya tabel itemsyang memiliki kolom pricetipe integer.

Saya mengalami masalah dalam menampilkan nilai harga dalam pandangan saya untuk harga yang mencakup Euro dan sen. Apakah saya kehilangan sesuatu yang jelas sejauh menyangkut mata uang dalam kerangka Rails?

Barry Gallagher
sumber
jika seseorang menggunakan sql, maka itu DECIMAL(19, 4) adalah pilihan populer periksa ini juga periksa di sini Format Mata Uang Dunia untuk memutuskan berapa banyak tempat desimal untuk digunakan, harap membantu.
shaijut

Jawaban:

495

Anda mungkin ingin menggunakan DECIMALjenis dalam basis data Anda. Dalam migrasi Anda, lakukan sesuatu seperti ini:

# 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

Di Rails, :decimaltipe dikembalikan sebagai BigDecimal, yang sangat bagus untuk perhitungan harga.

Jika Anda bersikeras menggunakan bilangan bulat, Anda harus mengkonversi secara manual ke dan dari BigDecimalmana-mana, yang mungkin hanya akan menyusahkan.

Seperti yang ditunjukkan oleh mcl, untuk mencetak harga, gunakan:

number_to_currency(price, :unit => "€")
#=> €1,234.01
Molf
sumber
13
Gunakan penolong number_to_currency, info lebih lanjut di api.rubyonrails.org/classes/ActionView/Helpers/…
mlibby
48
Sebenarnya, ini jauh lebih aman dan lebih mudah untuk menggunakan integer dalam kombinasi dengan act_as_dollars. Pernahkah Anda digigit oleh perbandingan floating-point? Jika tidak, jangan menjadikan ini pengalaman pertama Anda. :) Dengan act_as_dollars, Anda memasukkan barang dalam format 12,34, disimpan sebagai 1234, dan keluar sebagai 12,34.
Sarah Mei
50
@Sarah Mei: BigDecimals + format kolom desimal menghindari hal itu.
Molf
114
Sangat penting untuk tidak hanya menyalin jawaban ini secara membabi buta - presisi 8, skala 2 memberi Anda nilai maksimum 999.999,99 . Jika Anda membutuhkan angka lebih dari satu juta maka tingkatkan presisi!
Jon Cairns
22
Penting juga untuk tidak hanya menggunakan skala 2 secara membabi buta jika Anda menangani mata uang yang berbeda - beberapa mata uang utara-Afrika dan arab seperti Rial Oman atau Dinar Tunisia memiliki skala 3, jadi presisi 8 skala 3 lebih tepat di sana .
Kalahkan Richartz
117

Inilah pendekatan yang bagus dan sederhana yang memanfaatkan composed_of(bagian dari ActiveRecord, menggunakan pola ValueObject) dan permata Uang

Kamu akan membutuhkan

  • The Money permata (versi 4.1.0)
  • Model, misalnya Product
  • Sebuah integerkolom dalam model Anda (dan database), misalnya:price

Tulis ini di product.rbfile Anda :

class Product > ActiveRecord::Base

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

Apa yang akan Anda dapatkan:

  • Tanpa perubahan tambahan, semua formulir Anda akan menunjukkan dolar dan sen, tetapi representasi internal masih hanya sen. Formulir akan menerima nilai seperti "$ 12.034,95" dan mengonversinya untuk Anda. Tidak perlu menambahkan penangan atau atribut tambahan ke model Anda, atau bantuan dalam pandangan Anda.
  • product.price = "$12.00" secara otomatis dikonversi ke kelas Uang
  • product.price.to_s menampilkan angka berformat desimal ("1234.00")
  • product.price.format menampilkan string yang diformat dengan benar untuk mata uang
  • Jika Anda perlu mengirim sen (ke gateway pembayaran yang menginginkan uang), product.price.cents.to_s
  • Konversi mata uang gratis
Ken Mayer
sumber
14
Saya suka pendekatan ini. Tetapi harap dicatat: pastikan migrasi Anda untuk 'harga' dalam contoh ini tidak mengizinkan nol dan default ke 0 jangan sampai Anda menjadi gila mencoba mencari tahu mengapa ini tidak berhasil.
Cory
3
Saya menemukan permata money_column (diekstrak dari Shopify) sangat mudah digunakan ... lebih mudah daripada permata uang, jika Anda tidak memerlukan konversi mata uang.
talyric
7
Perlu dicatat untuk semua orang yang menggunakan permata Uang bahwa tim inti Rails sedang membahas penghinaan dan penghapusan "compos_of" dari kerangka kerja. Saya menduga permata akan diperbarui untuk menangani ini jika itu terjadi, tetapi jika Anda melihat Rails 4.0 Anda harus menyadari kemungkinan ini
Peer Allan
1
Mengenai komentar @ PeerAllan tentang penghapusan di composed_of sini adalah lebih rinci tentang itu serta implementasi alternatif.
HerbCSO
3
Juga, ini benar-benar mudah menggunakan permata rel-uang .
fotanus
25

Praktik umum untuk menangani mata uang adalah menggunakan tipe desimal. Berikut adalah contoh sederhana dari "Agile Web Development with Rails"

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

Ini akan memungkinkan Anda untuk menangani harga dari -999.999,99 hingga 999.999,99
Anda juga mungkin ingin menyertakan validasi dalam item Anda seperti

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

untuk kewarasan-periksa nilai-nilai Anda.

alex.zherdev
sumber
1
Solusi ini juga memungkinkan Anda menggunakan SQL sum dan teman-teman.
Larry K
4
Bisakah Anda melakukannya: memvalidasi: harga,: presence => true,: numericality => {: bigger_than => 0}
Galaxy
9

Jika Anda menggunakan Postgres (dan karena kami berada di tahun 2017 sekarang), Anda mungkin ingin memberikannya :money mencoba jenis kolomnya.

add_column :products, :price, :money, default: 0
The Whiz of Oz
sumber
7

Gunakan permata money-rails . Ini dengan baik menangani uang dan mata uang dalam model Anda dan juga memiliki banyak pembantu untuk memformat harga Anda.

Troggy
sumber
Ya, saya setuju dengan ini. Secara umum, saya menangani uang dengan menyimpannya sebagai sen (bilangan bulat) dan menggunakan permata seperti act-as-money atau money (money-rails) untuk menangani data dalam memori. Menanganinya dalam bilangan bulat mencegah kesalahan pembulatan yang tidak menyenangkan itu. Misalnya 0,2 * 3 => 0,600000000000000001 Ini, tentu saja, hanya berfungsi jika Anda tidak perlu menangani pecahan sen.
Chad M
Ini sangat bagus jika Anda menggunakan rel. Masukkan dan jangan khawatir tentang masalah dengan kolom desimal. Jika Anda menggunakan ini dengan tampilan, jawaban ini mungkin berguna juga: stackoverflow.com/questions/18898947/…
mooreds
6

Hanya sedikit pembaruan dan kohesi dari semua jawaban untuk beberapa calon junior / pemula dalam pengembangan RoR yang pasti akan datang ke sini untuk beberapa penjelasan.

Bekerja dengan uang

Gunakan :decimaluntuk menyimpan uang di DB, seperti yang disarankan @molf (dan apa yang digunakan perusahaan saya sebagai standar emas ketika bekerja dengan uang).

# 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

Beberapa poin:

  • :decimalakan digunakan sebagai BigDecimalyang memecahkan banyak masalah.

  • precisiondan scaleharus disesuaikan, tergantung pada apa yang Anda wakili

    • Jika Anda bekerja dengan menerima dan mengirim pembayaran, precision: 8dan scale: 2memberi Anda 999,999.99sebagai jumlah tertinggi, yang berlaku untuk 90% kasus.

    • Jika Anda perlu mewakili nilai properti atau mobil langka, Anda harus menggunakan yang lebih tinggi precision.

    • Jika Anda bekerja dengan koordinat (bujur dan lintang), Anda pasti membutuhkan yang lebih tinggi scale.

Cara menghasilkan migrasi

Untuk menghasilkan migrasi dengan konten di atas, jalankan di terminal:

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

atau

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

seperti yang dijelaskan di blog ini posting .

Pemformatan mata uang

KISS selamat tinggal di perpustakaan tambahan dan gunakan bantuan bawaan. Menggunakannumber_to_currency seperti yang disarankan @molf dan @facundofarias.

Untuk bermain dengan number_to_currencypembantu di konsol Rails, kirim panggilan ke ActiveSupport'sNumberHelper kelas untuk mengakses pembantu.

Sebagai contoh:

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

memberikan hasil sebagai berikut

2500000,61

Periksa lain optionsdari number_to_currency pembantu.

Di mana harus meletakkannya

Anda dapat memasukkannya ke dalam pembantu aplikasi dan menggunakannya di dalam tampilan berapa pun jumlahnya.

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

Atau Anda dapat memasukkannya ke dalam Itemmodel sebagai metode instan, dan menyebutnya di mana Anda perlu memformat harga (dalam tampilan atau bantuan).

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

Dan, contoh bagaimana saya menggunakan bagian number_to_currencydalam contrroler (perhatikan negative_formatopsi, digunakan untuk mewakili pengembalian uang)

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

Menggunakan Atribut Virtual (Tautan ke Railscast yang direvisi (berbayar)) Anda dapat menyimpan price_in_cents Anda dalam kolom integer dan menambahkan atribut virtual price_in_dollars dalam model produk Anda sebagai pengambil dan penyetel.

# 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

Sumber: Railscasts # 016: Atribut Virtual : atribut Virtual adalah cara yang bersih untuk menambahkan kolom formulir yang tidak peta langsung ke database. Di sini saya menunjukkan cara menangani validasi, asosiasi, dan banyak lagi.

Thomas Klemm
sumber
1
ini menyisakan 200,0 satu digit
ajbraus
2

Bilangan bulat pasti .

Dan meskipun BigDecimal ada secara teknis 1.5masih akan memberi Anda Float murni di Ruby.

bisa diperdebatkan
sumber
2

Jika seseorang menggunakan Sekuel migrasi akan terlihat seperti:

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

entah bagaimana Sequel mengabaikan: presisi dan: skala

(Versi Sekuel: sekuel (3.39.0, 3.38.0))

Yitroo
sumber
2

API dasar saya semuanya menggunakan sen untuk mewakili uang, dan saya tidak ingin mengubahnya. Saya juga tidak bekerja dengan banyak uang. Jadi saya hanya menempatkan ini dalam metode pembantu:

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

Itu mengubah bilangan bulat ke string dengan setidaknya tiga digit (menambahkan nol terkemuka jika perlu), lalu memasukkan titik desimal sebelum dua digit terakhir, tidak pernah menggunakan Float . Dari sana Anda dapat menambahkan simbol mata uang apa pun yang sesuai untuk kasus penggunaan Anda.

Ini jelas cepat dan kotor, tetapi kadang-kadang itu tidak masalah!

Brent Royal-Gordon
sumber
Tidak percaya tidak ada yang mengangkat Anda. Ini adalah satu-satunya hal yang berfungsi untuk mendapatkan objek Uang saya dengan baik ke dalam bentuk sehingga API dapat mengambilnya. (Desimal)
Code-MonKy
2

Saya menggunakannya dengan cara ini:

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

Tentu saja simbol mata uang, ketepatan, format dan sebagainya tergantung pada setiap mata uang.

facundofaria
sumber
1

Anda dapat memberikan beberapa opsi ke number_to_currency(pembantu tampilan Rails 4 standar):

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

Seperti yang diposting oleh Dylan Markow

blnc
sumber
0

Kode sederhana untuk Ruby & Rails

<%= number_to_currency(1234567890.50) %>

OUT PUT => $1,234,567,890.50
Dinesh Vaitage
sumber