Який найкращий метод поводження з валютою / грошима?


323

Я працюю над дуже базовою системою кошиків для покупок.

У мене є таблиця items, в якій стовпчик priceтипу integer.

У мене виникають проблеми з відображенням ціни в моїх поглядах на ціни, що включають як євро, так і центи. Я пропускаю щось очевидне, що стосується обробки валюти в рамках Rails?


якщо хтось використовує sql, то DECIMAL(19, 4) це популярний вибір, перевірте це, також перевірте тут Формати світової валюти, щоб визначити, скільки десяткових знаків використовувати, надію, допоможе.
shaijut

Відповіді:


495

Можливо, ви захочете використовувати DECIMALтип у вашій базі даних. Під час міграції зробіть щось подібне:

# 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

У Rails :decimalтип повертається як BigDecimal, що відмінно підходить для розрахунку ціни.

Якщо ви наполягаєте на використанні цілих чисел, вам доведеться вручну перетворюватися на BigDecimalвсюди та звідси , що, ймовірно, просто стане болем.

Як вказує mcl, для друку ціни використовуйте:

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

13
Скористайтеся помічником number_to_currency
mlibby

48
Насправді, набагато безпечніше і простіше використовувати ціле число в поєднанні з act_as_dollars. Вас коли-небудь кусав порівняння з плаваючою комою? Якщо ні, не робіть це своїм першим досвідом. :) За допомогою act_as_dollars ви поміщаєте речі у форматі 12,34, він зберігається як 1234, а виходить як 12,34.
Сара Мей

50
@Sarah Mei: формат BigDecimals + десятковий стовпець уникає саме цього.
мольф

114
Важливо не просто копіювати цю відповідь наосліп - точність 8, шкала 2 дає максимальне значення 999,999,99 . Якщо вам потрібно число більше мільйона, то збільште точність!
Джон Кернс

22
Також важливо не просто сліпо використовувати шкалу 2, якщо ви обробляєте різні валюти - деякі північноафриканські та арабські валюти, як Оманський ріал або Туніський динар, мають шкалу 3, тому точність 8 за шкалою 3 більше доречна .
Удар Річарца

117

Ось чудовий простий підхід, який використовує composed_of(частина ActiveRecord, використовуючи шаблон ValueObject) та дорогоцінний камінь

Вам знадобиться

  • Гроші камінь (версія 4.1.0)
  • Модель, наприклад Product
  • integerСтовпець у вашій моделі (і бази даних), наприклад ,:price

Запишіть це у свій product.rbфайл:

class Product > ActiveRecord::Base

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

Що ви отримаєте:

  • Без зайвих змін усі ваші форми показуватимуть долари та центи, але внутрішнє представництво - це лише центи. Форми прийматимуть такі значення, як "12,034,95 долара" та конвертують їх для вас. Не потрібно додавати додаткові обробники чи атрибути до вашої моделі чи помічників на ваш погляд.
  • product.price = "$12.00" автоматично переходить у Грошовий клас
  • product.price.to_s відображає десятичне відформатоване число ("1234.00")
  • product.price.format відображається правильно відформатований рядок для валюти
  • Якщо вам потрібно надіслати центи (до шлюзу платежів, який хоче копійки), product.price.cents.to_s
  • Конвертація валюти безкоштовно

14
Я люблю такий підхід. Але зауважте: переконайтеся, що ваша міграція на "ціну" в цьому прикладі не дозволяє нулю і значень за замовчуванням до 0, щоб не звести з розуму, намагаючись з'ясувати, чому це не працює.
Cory

3
Я виявив, що дорогоцінний камінь money_column (витягнутий з Shopify) дуже прямо вперед для використання ... простіше, ніж дорогоцінний камінь, якщо вам не потрібна конвертація валюти.
талірік

7
Слід зазначити, що всі, хто використовує дорогоцінний камінь, про те, що основна команда Rails обговорює знецінення та вилучення "композиції_of" із рамки. Я підозрюю, що дорогоцінний камінь буде оновлений, щоб вирішити це, якщо це станеться, але якщо ви дивитесь на Rails 4.0, ви повинні знати про цю можливість
Peer Allan

1
Що стосується коментаря @ PeerAllan про видалення composed_of тут, то це детальніше, а також альтернативна реалізація.
HerbCSO

3
Крім того , це дійсно reasy використовуючи рейки-гроші дорогоцінний камінь.
fotanus

25

Поширеною практикою поводження з валютою є використання десяткового типу. Ось простий приклад з "Agile Web Development with Rails"

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

Це дозволить вам обробляти ціни від -999,999,99 до 999,999,99
Ви також можете включити перевірку в такі товари, як

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

щоб здорово перевірити свої цінності.


1
Це рішення також дозволяє використовувати суму SQL та друзів.
Ларрі К

4
Чи можете ви зробити: валідація: ціна,: присутність => істина,: числовість => {: більший_з = = 0}
Галактика,

9

Якщо ви використовуєте Postgres (а оскільки ми зараз у 2017 році), ви :moneyможете спробувати ввести їх колонку.

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

7

Використовуйте дорогоцінний камінь . Він чудово обробляє гроші та валюти у вашій моделі, а також має купу помічників для форматування ваших цін.


Так, я згоден з цим. Як правило, я обробляю гроші, зберігаючи їх у вигляді копійок (ціле число) та використовуючи дорогоцінний камінь, як акти-гроші або гроші (грошові рейки) для обробки даних у пам'яті. Поводження з цілими числами запобігає помилкам помилок округлення. Напр. 0,2 * 3 => 0,6000000000000001 Це, звичайно, працює лише в тому випадку, якщо вам не потрібно обробляти частки відсотка.
Чад М

Це дуже приємно, якщо ви використовуєте рейки. Залиште його та не хвилюйтесь з приводу питань із десятковим стовпцем. Якщо ви використовуєте це з тим, що ця відповідь може бути корисним , а також: stackoverflow.com/questions/18898947 / ...
mooreds

6

Лише невелике оновлення та згуртованість усіх відповідей для деяких починаючих юніорів / початківців у розробці RoR, які неодмінно прийдуть сюди за деякими поясненнями.

Робота з грошима

Використовуйте :decimalдля зберігання грошей у БД, як запропонував @molf (і те, що моя компанія використовує як золотий стандарт при роботі з грошима).

# 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

Кілька пунктів:

  • :decimalбуде використовуватися, BigDecimalщо вирішує багато питань.

  • precisionі scaleмає бути скоригована залежно від того, що ви представляєте

    • Якщо ви працюєте з отриманням та відправленням платежів, precision: 8і це scale: 2дає вам 999,999.99як найвищу суму, що штрафується в 90% випадків.

    • Якщо вам потрібно представити вартість нерухомості або рідкісного автомобіля, вам слід скористатися вищою precision.

    • Якщо ви працюєте з координатами (довгота і широта), вам точно знадобиться більший scale.

Як створити міграцію

Щоб створити міграцію з вищевказаним вмістом, запустіть у терміналі:

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

або

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

як пояснено в цій публікації в блозі .

Форматування валюти

Поцілуйте зайві бібліотеки та використовуйте вбудовані помічники. Використовуйте number_to_currencyяк запропоновано @molf та @facundofarias.

Для того, щоб грати з number_to_currencyпомічником в консолі Rails, здійснити дзвінок до ActiveSupport«s NumberHelperкласу для доступу помічника.

Наприклад:

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

дає наступний вихід

2500000,61

Перевірте інший optionsз number_to_currency помічника.

Куди його поставити

Ви можете помістити його в помічник програми та використовувати його у внутрішніх переглядах на будь-яку суму.

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

Або ви можете помістити його в Itemмодель як метод екземпляра і назвати її там, де потрібно відформатувати ціну (у переглядах або помічниках).

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

І, наприклад, як я використовую number_to_currencyвнутрішній контролер (зверніть увагу на negative_formatпараметр, який використовується для відображення відшкодувань)

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

5

Використовуючи віртуальні атрибути (посилання на переглянутий (оплачений) Railscast), ви можете зберігати свою ціну_in_cents у цілому стовпці та додавати віртуальний атрибут price_in_dollars у вашу модель продукту як геттер і сеттер.

# 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

Джерело: RailsCasts # 016: Віртуальні атрибути : Віртуальний атрибут - це чистий спосіб додавання полів форми, які не відображаються безпосередньо в базу даних. Тут я показую, як обробляти валідації, асоціації тощо.


1
це залишає 200,0 однієї цифри
айбраус


2

Якщо хтось використовує Sequel, міграція виглядає приблизно так:

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

якось Секвель ігнорує: точність і: масштаб

(Версія продовження: продовження (3.39.0, 3.38.0))


2

Всі мої основні API використовували центи для представлення грошей, і я не хотів цього змінювати. Я також не працював з великими сумами грошей. Тому я просто вклав це в помічний метод:

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

Це перетворює ціле число у рядок щонайменше з трьох цифр (додаючи при цьому необхідні нулі), потім вставляє десяткову крапку перед останніми двома цифрами, ніколи не використовуючи a Float. Звідти ви можете додати будь-які символи валюти, які відповідають вашому випадку використання.

Це, безумовно, швидко і брудно, але іноді це просто чудово!


Не можу повірити, що ніхто не звернувся до тебе. Це було єдине, що працювало над тим, щоб мій об’єкт "Гроші" чудово вийшов у форму, таку можливість API може прийняти. (
Десятима

2

Я використовую його таким чином:

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

Звичайно, що символ валюти, точність, формат і так далі залежить від кожної валюти.


1

Ви можете передати деякі варіанти number_to_currency(стандартний помічник Rails 4):

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

Як опублікував Ділан Марков


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.