Каков наилучший метод обращения с валютой / деньгами?



Я работаю над очень простой системой корзины покупок.



у меня есть таблица items Это имеет столбец price типа integer.



у меня возникли проблемы с отображением значения цены в моих представлениях для цен, которые включают в себя как евро, так и центы. Я упускаю что-то очевидное в том, что касается обработки валюты в рамках Rails?

640   13  

13 ответов:

вы, вероятно, захотите использовать 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

в рельсах,:decimal тип возвращается как BigDecimal, что отлично подходит для расчета цены.

если вы настаиваете на использовании целых чисел, вам придется вручную конвертировать в и из BigDecimals везде, что, вероятно, просто станет болью.

как указал mcl, чтобы напечатать цену, используйте:

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

вот прекрасный, простой подход, который использует composed_of (часть ActiveRecord, используя шаблон ValueObject) и Money gem

вам понадобится

  • The Money gem (версия 4.1.0)
  • модель, например Product
  • An integer столбец в вашей модели (и базе данных), например :price

напишите это в своем product.rb file:

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 = ".00" автоматически преобразуется в класс Money
  • product.price.to_s отображает десятичное число в формате ("1234.00")
  • product.price.format отображает правильно отформатированную строку для валюты
  • Если вам нужно отправить центы (на платежный шлюз, который хочет Пенни),product.price.cents.to_s
  • конвертация валюты бесплатно!--18-->

обычно для обработки валюты используется десятичный тип. Вот простой пример из "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 

для здравомыслия-Проверьте свои значения.

использовать money-rails gem. Он хорошо обрабатывает деньги и валюты в вашей модели, а также имеет кучу помощников для форматирования ваших цен.

С помощью виртуальные атрибуты (ссылка на пересмотренный (платный) Railscast) вы можете хранить price_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: Виртуальные Атрибуты:виртуальные атрибуты-это чистый способ, чтобы добавить поля, которые не отображаются непосредственно в базу данных. Здесь я покажу, как обрабатывать проверки, ассоциации и больше.

Если вы используете Postgres (и так как мы сейчас находимся в 2017 году), вы можете дать их :money тип столбца попробовать.

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

наверняка чисел.

и хотя bigdecimal технически существует 1.5 все равно даст вам чистый поплавок в Ruby.

Если кто-то использует продолжение миграции будет выглядеть примерно так:

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

Как-то сиквел игнорирует :точность и :масштаб

(продолжение версии: Продолжение (3.39.0, 3.38.0))

Я использую его таким образом:

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

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

вы можете передать некоторые параметры в number_to_currency (стандартный Rails 4 view helper):

number_to_currency(12.0, :precision => 2)
# => ".00"

Как написал Дилан Markow

все мои базовые API использовали центы для представления денег, и я не хотел этого менять. Я не работаю с большими суммами денег. Поэтому я просто положил это в вспомогательный метод:

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

который преобразует целое число в строку с не менее чем тремя цифрами (при необходимости добавляя начальные нули), а затем вставляет десятичную точку перед последними двумя цифрами, никогда не используя Float. Оттуда вы можете добавить любые символы валюты, подходящие для вашего использования случай.

Это наверняка быстро и грязно, но иногда это просто прекрасно!

простой код для Ruby & Rails

<%= number_to_currency(1234567890.50) %>

OUT PUT => ,234,567,890.50

просто небольшое обновление и сплоченность всех ответов для некоторых начинающих юниоров/новичков в развитии 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

Comments

    Ничего не найдено.