Skip to content
/ franca Public

A money engine system that models Money and Currency

Notifications You must be signed in to change notification settings

Absesay/franca

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 

Repository files navigation

Franca

A Ruby library for handling money and currency operations with precision, type safety, and financial best practices. Built for BFSI (Banking, Financial Services, and Insurance) applications where accuracy and proper currency handling are critical.

Features

  • 💰 Money Value Objects - Immutable money objects with BigDecimal precision (no floating-point errors)
  • 🌍 Currency Support - ISO 4217 currency codes with symbols, names, and decimal places
  • 💱 Currency Conversion - Exchange rate management and multi-currency conversions
  • 🔢 Multiple Rounding Strategies - Banker's rounding, floor, ceiling, and more
  • 🎨 Flexible Formatting - Locale-aware currency formatting with customizable options
  • Arithmetic Operations - Safe addition, subtraction, multiplication, and division
  • 🔍 Type Safety - Prevents mixing different currencies in operations
  • 🧊 Immutable Objects - Value objects that are frozen for thread safety

Installation

Add the lib directory to your Ruby load path:

$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "lib"))

Or require files directly:

require_relative "lib/money"
require_relative "lib/currency"

Quick Start

require_relative "lib/money"
require_relative "lib/currency"

# Create money objects
hundred_dollars = Money.new(100, :USD)
fifty_euros = Money.new(50, :EUR)

# Arithmetic operations
total = hundred_dollars + Money.new(50, :USD)  # => Money(150, USD)
half = hundred_dollars / 2                     # => Money(50, USD)
doubled = hundred_dollars * 2                  # => Money(200, USD)

# Currency conversion
require_relative "lib/exchange_rate_loader"
ExchangeRateLoader.load_default_rates
eur_money = hundred_dollars.convert_to(:EUR)   # => Money(85, EUR)

# Formatting
require_relative "lib/formatters"
puts Formatters.format(hundred_dollars)        # => "$100.00"

Usage Examples

Working with Currency

usd = Currency.new(:USD)
puts usd.symbol        # => "$"
puts usd.name          # => "US Dollar"
puts usd.decimal_places # => 2

jpy = Currency.new(:JPY)
puts jpy.has_decimals? # => false (Yen has no decimal places)

Creating Money Objects

# From numeric values
money1 = Money.new(100, :USD)
money2 = Money.new(99.99, :EUR)

# From strings (useful for parsing user input)
money3 = Money.new("100.50", :USD)

# Parse from string format
money4 = Money.parse("99.99 USD")  # => Money(99.99, USD)

# Create zero money
zero = Money.zero(:USD)  # => Money(0, USD)

Arithmetic Operations

hundred = Money.new(100, :USD)
fifty = Money.new(50, :USD)

# Addition
total = hundred + fifty  # => Money(150, USD)

# Subtraction
difference = hundred - fifty  # => Money(50, USD)

# Multiplication (scalar)
doubled = hundred * 2  # => Money(200, USD)

# Division (scalar)
half = hundred / 2  # => Money(50, USD)

# Mixing currencies raises an error
# hundred + Money.new(50, :EUR)  # => ArgumentError

Comparison Operations

money1 = Money.new(100, :USD)
money2 = Money.new(200, :USD)

money1 < money2   # => true
money1 > money2   # => false
money1 == money2  # => false

# Sort money objects
amounts = [Money.new(300, :USD), Money.new(100, :USD), Money.new(200, :USD)]
amounts.sort  # => [Money(100, USD), Money(200, USD), Money(300, USD)]

Rounding Strategies

money = Money.new(99.995, :USD)

money.round(:default)  # => Money(100.00, USD) - Standard rounding (half up)
money.round(:banker)   # => Money(100.00, USD) - Banker's rounding (half to even)
money.round(:floor)    # => Money(99.99, USD)  - Always round down
money.round(:ceiling)   # => Money(100.00, USD) - Always round up

Currency Conversion

require_relative "lib/exchange_rate_loader"

# Load exchange rates
ExchangeRateLoader.load_default_rates

# Convert money
usd_money = Money.new(100, :USD)
eur_money = usd_money.convert_to(:EUR)  # => Money(85, EUR)
gbp_money = usd_money.convert_to(:GBP)  # => Money(73, GBP)
jpy_money = usd_money.convert_to(:JPY)  # => Money(11000, JPY)

# Convert back
usd_again = eur_money.convert_to(:USD)  # => Money(100, USD)

# Set custom exchange rates
ExchangeRateLoader.set_rate(:USD, :BTC, 0.000025)
btc_money = usd_money.convert_to(:BTC)

Formatting Money

require_relative "lib/formatters"

money = Money.new(1234.56, :USD)

# Default formatting
Formatters.format(money)  # => "$1,234.56"

# Custom options
Formatters.format(money, symbol_position: :after)  # => "1,234.56 $"
Formatters.format(money, show_symbol: false)       # => "1,234.56"

# Different currencies have different formats
Formatters.format(Money.new(1234.56, :EUR))  # => "€1.234,56" (European format)
Formatters.format(Money.new(1234, :JPY))     # => "¥1,234" (no decimals)

Real-World Scenarios

Calculate Transaction Total

transactions = [
  Money.new(29.99, :USD),
  Money.new(15.50, :USD),
  Money.new(8.75, :USD),
]
total = transactions.reduce(Money.zero(:USD)) { |sum, money| sum + money }
puts total  # => "$54.24"

Calculate Fee with Rounding

principal = Money.new(1000, :USD)
fee_rate = 0.025
fee = (principal * fee_rate).round(:ceiling)  # Always round fees up
total = principal + fee

Multi-Currency Portfolio

ExchangeRateLoader.load_default_rates

portfolio = {
  USD: Money.new(1000, :USD),
  EUR: Money.new(500, :EUR),
  GBP: Money.new(300, :GBP),
}

# Convert all to USD for total value
total_usd = portfolio.values.reduce(Money.zero(:USD)) do |sum, money|
  sum + money.convert_to(:USD)
end

Supported Currencies

The library supports the following ISO 4217 currencies:

  • USD - US Dollar ($)
  • EUR - Euro (€)
  • GBP - British Pound (£)
  • JPY - Japanese Yen (¥) - No decimal places
  • CAD - Canadian Dollar (C$)
  • AUD - Australian Dollar (A$)
  • CHF - Swiss Franc (CHF)
  • CNY - Chinese Yuan (¥)

Architecture

Value Objects

Both Money and Currency are implemented as value objects:

  • Immutable (frozen after creation)
  • Equality based on value, not object identity
  • Safe to use in hashes and sets
  • Thread-safe

BigDecimal Precision

All monetary amounts use BigDecimal instead of Float to avoid precision errors:

# Float (WRONG for money)
0.1 + 0.2  # => 0.30000000000000004

# BigDecimal (CORRECT)
BigDecimal('0.1') + BigDecimal('0.2')  # => 0.3e0

Rounding Strategies

The library supports multiple rounding modes for different financial contexts:

  • :default / :half_up - Standard rounding (0.5 rounds up)
  • :banker / :half_even - Banker's rounding (reduces bias in large datasets)
  • :floor / :down - Always round down (used in tax calculations)
  • :ceiling / :up - Always round up (used in fee calculations)
  • :truncate - Truncate toward zero

Running the Examples

The project includes a comprehensive example file demonstrating all features:

ruby app/franca.rb

Project Structure

franca/
├── lib/
│   ├── currency.rb              # Currency value object
│   ├── money.rb                 # Money value object
│   ├── exchange_rate_loader.rb  # Exchange rate management
│   ├── rounding_strategies.rb  # Rounding implementations
│   └── formatters.rb            # Currency formatting
└── app/
    └── franca.rb                # Usage examples

Design Principles

  1. Precision First - Uses BigDecimal to avoid floating-point errors
  2. Type Safety - Prevents invalid operations (e.g., mixing currencies)
  3. Immutability - All value objects are frozen for safety
  4. Financial Best Practices - Implements proper rounding and formatting
  5. Extensibility - Easy to add new currencies and formatting rules

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

[some day]


Note: This library is designed for educational and development purposes. For production financial systems, consider additional features like:

  • Real-time exchange rate APIs
  • More comprehensive currency support
  • Localization/internationalization
  • Performance optimizations
  • Comprehensive test coverage

About

A money engine system that models Money and Currency

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages