philiprehberger-money

Immutable money value object with integer subunit storage and multi-currency formatting
Requirements
Installation
Add to your Gemfile:
gem "philiprehberger-money"
Or install directly:
gem install philiprehberger-money
Usage
require "philiprehberger/money"
price = Philiprehberger::Money.new(1999, :usd)
price.amount price.to_s
Arithmetic
require "philiprehberger/money"
a = Philiprehberger::Money.new(1000, :usd)
b = Philiprehberger::Money.new(500, :usd)
sum = a + b diff = a - b mult = a * 2 quot = a / 3 neg = -a abs = neg.abs
require "philiprehberger/money"
usd = Philiprehberger::Money.new(1999, :usd)
usd.format usd.format(code: true) usd.format(symbol: false)
eur = Philiprehberger::Money.new(1999, :eur)
eur.format
jpy = Philiprehberger::Money.new(2000, :jpy)
jpy.format
Parsing
require "philiprehberger/money"
Philiprehberger::Money.parse("$1,234.56") Philiprehberger::Money.parse("1.234,56 EUR") Philiprehberger::Money.parse("¥2000", currency: :jpy) Philiprehberger::Money.parse("-$19.99")
Percentage Operations
require "philiprehberger/money"
price = Philiprehberger::Money.new(10000, :usd)
price.percent(15) price.add_percent(20) price.subtract_percent(25)
Tax Breakdown
require "philiprehberger/money"
net = Philiprehberger::Money.new(10000, :usd) result = net.tax_breakdown(0.2)
result[:net].to_s result[:tax].to_s result[:gross].to_s
Clamping
require "philiprehberger/money"
min = Philiprehberger::Money.new(500, :usd) max = Philiprehberger::Money.new(2000, :usd)
Philiprehberger::Money.new(1000, :usd).clamp(min, max).to_s Philiprehberger::Money.new(100, :usd).clamp(min, max).to_s Philiprehberger::Money.new(5000, :usd).clamp(min, max).to_s
Allocation
require "philiprehberger/money"
total = Philiprehberger::Money.new(1000, :usd)
shares = total.allocate([0.5, 0.3, 0.2])
shares.map(&:cents)
odd = Philiprehberger::Money.new(100, :usd)
parts = odd.allocate([1, 1, 1])
parts.map(&:cents) parts.sum(&:cents)
Split
require "philiprehberger/money"
total = Philiprehberger::Money.new(1000, :usd)
parts = total.split(3)
parts.map(&:cents) parts.sum(&:cents)
Rounding Modes
require "philiprehberger/money"
Philiprehberger::Money.from_amount(0.025, :usd)
Philiprehberger::Money.from_amount(0.025, :usd, rounding: :half_up)
Philiprehberger::Money.from_amount(0.021, :usd, rounding: :ceil)
Philiprehberger::Money.from_amount(0.029, :usd, rounding: :floor)
Custom Currencies
require "philiprehberger/money"
Philiprehberger::Money::Currency.register(
code: "XAU",
name: "Gold Troy Ounce",
symbol: "Au",
subunit_to_unit: 100,
symbol_first: true
)
gold = Philiprehberger::Money.from_amount(1950.50, :xau)
gold.format
Currency Conversion
require "philiprehberger/money"
usd = Philiprehberger::Money.new(1000, :usd)
eur = usd.convert_to(:eur, rate: 0.85)
eur.cents eur.currency.code
Exchange Rate Store
require "philiprehberger/money"
Philiprehberger::Money::ExchangeRate.store.set(:USD, :EUR, 0.85)
Philiprehberger::Money::ExchangeRate.store.set(:USD, :GBP, 0.73)
usd = Philiprehberger::Money.new(1000, :usd)
eur = usd.exchange_to(:EUR)
eur.cents
eur_amount = Philiprehberger::Money.new(850, :eur)
back_to_usd = eur_amount.exchange_to(:USD)
moneys = [
Philiprehberger::Money.new(1000, :usd),
Philiprehberger::Money.new(850, :eur)
]
total = Philiprehberger::Money.sum(moneys, target_currency: :USD)
total.cents
Philiprehberger::Money::ExchangeRate.store.clear
Rounding to Nearest Increment
require "philiprehberger/money"
price = Philiprehberger::Money.new(123, :usd)
price.round_to_nearest(5).cents price.round_to_nearest(10).cents price.round_to_nearest(25).cents
Rounding
require "philiprehberger/money"
jpy = Philiprehberger::Money.new(2000, :jpy)
jpy.currency.exponent jpy.round.cents
usd = Philiprehberger::Money.from_amount(1.234, :usd)
usd.currency.exponent usd.round(0).cents usd.round(1).cents usd.round.cents
Serialization
price = Philiprehberger::Money.new(1999, :usd)
price.to_h
Pattern Matching
case Philiprehberger::Money.new(1999, :usd)
in { currency: :usd, amount: Float => amt }
puts "USD amount: #{amt}"
end
Comparison
require "philiprehberger/money"
a = Philiprehberger::Money.new(1000, :usd)
b = Philiprehberger::Money.new(2000, :usd)
a < b a == b a.zero?
API
Money class methods
| Method |
Description |
.new(cents, currency_code) |
Create from integer subunits and currency code |
.from_amount(amount, currency_code, rounding:) |
Create from decimal amount with configurable rounding |
.parse(string, currency:) |
Parse a formatted money string into a Money object |
.sum(moneys, target_currency:) |
Sum a collection of Money objects into a target currency |
Money instance methods
| Method |
Description |
#cents |
Integer subunit amount |
#currency |
Currency object with code, symbol, and formatting rules |
#amount |
BigDecimal representation of the amount |
#to_f |
Float representation of the amount |
#rounding_mode |
The rounding mode used for arithmetic |
#+(other) |
Add two same-currency Money objects |
#-(other) |
Subtract two same-currency Money objects |
#*(numeric) |
Multiply by a number (uses stored rounding mode) |
#/(numeric) |
Divide by a number (uses stored rounding mode) |
#-@ |
Negate the amount |
#abs |
Absolute value |
#percent(n) |
Return n% of the amount |
#add_percent(n) |
Return money + n% |
#subtract_percent(n) |
Return money - n% |
#tax_breakdown(rate) |
Return hash with net, tax, and gross Money objects |
#clamp(min, max) |
Constrain value within same-currency bounds |
#allocate(ratios) |
Split by ratios using largest remainder method |
#split(n) |
Split equally among n parts |
#format(symbol:, code:, thousands:) |
Format as string with options |
#to_s |
Format with default options |
#convert_to(target_code, rate:) |
Convert to another currency |
#exchange_to(target_code) |
Convert using the ExchangeRate store |
#round_to_nearest(increment) |
Round to nearest N subunits |
#round(precision = nil) |
Round to N decimal places (defaults to currency exponent) |
#zero? |
True if amount is zero |
#positive? |
True if amount is positive |
#negative? |
True if amount is negative |
#<=>(other) |
Compare same-currency amounts |
#to_h |
Hash with cents, amount, currency, formatted |
#deconstruct_keys(keys) |
Pattern matching support for case/in |
#eql?(other) |
Value equality check |
#hash |
Hash for use as hash key |
ExchangeRate methods
| Method |
Description |
.store |
Returns the singleton exchange rate store |
.reset! |
Resets the store (clears all rates) |
#set(from, to, rate) |
Set a conversion rate between two currencies |
#get(from, to) |
Get a conversion rate (resolves inverse automatically) |
#clear |
Remove all stored rates |
#rates_count |
Number of stored rates |
Currency class methods
| Method |
Description |
.find(code) |
Look up a currency by code |
.register(code:, name:, symbol:, ...) |
Register a custom currency |
Currency instance methods
| Method |
Description |
#code |
ISO 4217 currency code (lowercase symbol, e.g. :usd) |
#name |
Full currency name |
#symbol |
Currency symbol |
#subunit_to_unit |
Number of subunits per unit (100 for cents, 1 for zero-decimal) |
#exponent |
Number of decimal places for the currency (e.g. 2 for USD, 0 for JPY) |
#symbol_first |
Whether the symbol appears before the amount |
#decimal_separator |
Character separating decimals |
#thousands_separator |
Character separating thousands |
Development
bundle install
bundle exec rspec
bundle exec rubocop
Support
If you find this project useful:
⭐ Star the repo
🐛 Report issues
💡 Suggest features
❤️ Sponsor development
🌐 All Open Source Projects
💻 GitHub Profile
🔗 LinkedIn Profile
License
MIT