buckybox/crazy_money

View on GitHub
lib/crazy_money.rb

Summary

Maintainability
A
2 hrs
Test Coverage
require "bigdecimal"
require "i18n"
require "currency_data"

class CrazyMoney
  include Comparable

  module Configuration
    class << self
      attr_accessor :current_currency
    end
  end

  def self.zero
    new 0
  end

  def initialize(amount)
    @amount = BigDecimal.new(amount.to_s)
  end

  def to_s(decimal_places: 2)
    if current_currency = Configuration.current_currency
      decimal_places = currency(current_currency).decimal_places
    end

    sprintf("%.#{decimal_places}f", @amount)
  end

  def to_i
    @amount.to_i
  end

  def to_k
    if @amount.abs < 1E3
      amount, suffix = @amount, ""
    else
      amount, suffix = (@amount / 1E3), "k"
    end

    "#{amount.round.to_i}#{suffix}".freeze
  end

  def inspect
    "#<CrazyMoney amount=#{self}>"
  end

  def ==(other)
    @amount == BigDecimal.new(other.to_s)
  end
  alias_method :eql?, :==

  def <=>(other)
    @amount <=> BigDecimal.new(other.to_s)
  end

  def positive?
    @amount > 0
  end

  def negative?
    @amount < 0
  end

  def zero?
    @amount.zero?
  end

  def opposite
    self.class.new(self * -1)
  end

  def round(*args)
    self.class.new(@amount.round(*args))
  end

  def cents(ratio = 100)
    @amount * BigDecimal.new(ratio.to_s)
  end

  def +(other); self.class.new(@amount + BigDecimal.new(other.to_s)); end

  def -(other); self.class.new(@amount - BigDecimal.new(other.to_s)); end

  def /(other); self.class.new(@amount / BigDecimal.new(other.to_s)); end

  def *(other); self.class.new(@amount * BigDecimal.new(other.to_s)); end

  # FIXME: needs polishing
  def with_currency(iso_code)
    currency = currency(iso_code) || raise(ArgumentError, "Unknown currency: #{iso_code.inspect}")

    left, right = to_s(decimal_places: currency.decimal_places).split(".")
    decimal_mark = right.nil? ? "" : currency.decimal_mark
    sign = left.slice!("-")

    left = left.reverse.scan(/.{1,3}/).map(&:reverse).reverse # split every 3 digits right-to-left
           .join(thousands_separator)

    formatted = [sign, left, decimal_mark, right].join

    if currency.symbol_first
      [currency.prefered_symbol, formatted]
    else
      [formatted, " ", currency.prefered_symbol]
    end.join
  end

private

  def thousands_separator
    default = " ".freeze
    I18n.t("number.currency.format.thousands_separator", default: default)
  rescue I18n::InvalidLocale
    default
  end

  def currency(iso_code)
    ::CurrencyData.find(iso_code)
  end
end