require 'bigdecimal' require 'danconia/errors/exchange_rate_not_found' module Danconia class Money include Comparable attr_reader :amount, :currency, :decimals, :exchange def initialize amount, currency_code = nil, decimals: 2, exchange: Danconia.config.default_exchange @decimals = decimals @amount = parse amount @currency = Currency.find(currency_code || Danconia.config.default_currency, exchange) @exchange = exchange end def format decimals: @decimals, **other_options opts = other_options.reverse_merge precision: decimals, unit: currency.symbol ActiveSupport::NumberHelper.number_to_currency amount, opts end alias to_s format def inspect "#{amount} #{currency.code}" end def == other if other.is_a?(Money) amount == other.amount && currency == other.currency else amount == other && currency.code == Danconia.config.default_currency end end def eql? other self == other end def hash [amount, currency].hash end def <=> other other = other.exchange_to(currency).amount if other.is_a? Money amount <=> other end def exchange_to other_currency other_currency = other_currency.presence && Currency.find(other_currency, exchange) || currency rate = exchange.rate currency.code, other_currency.code clone_with amount * rate, other_currency end %w(+ - * /).each do |op| class_eval <<-EOR, __FILE__, __LINE__ + 1 def #{op} other other = other.exchange_to(currency).amount if other.is_a? Money clone_with amount #{op} other end EOR end def round *args clone_with amount.round(*args) end def in_cents (self * 100).round end def as_json *args amount.as_json *args end def default_currency? currency.code == Danconia.config.default_currency end def method_missing method, *args if @amount.respond_to? method @amount.send method, *args else super end end def respond_to? method, *args super or @amount.respond_to?(method, *args) end private def parse object BigDecimal(object.to_s) rescue BigDecimal(0) end def clone_with amount, currency = @currency Money.new amount, currency, decimals: decimals, exchange: exchange end end end