Class: WhittakerTech::Midas::Coin::Allocation

Inherits:
Object
  • Object
show all
Defined in:
app/models/whittaker_tech/midas/coin/allocation.rb

Overview

Allocation represents a per-unit interpretation of a Coin.

Conceptual model

Coin       -> canonical, payable money (what exists)
Allocation -> "this Coin, spread across N units, using policy X"

An Allocation answers two questions:

  1. What does one unit cost?#value
  2. What does a given quantity cost?#price

Immutability

Allocation is:

  • immutable@coin, @divisor, and @rounding_policy are frozen on construction and never mutated.
  • not persisted — Allocation is a pure value object.
  • minor-unit safe — it never stores sub-minor-unit amounts; rounding is applied before returning a Coin.

See also: Coin#allocate

Examples:

Per-unit price from a bulk price

bulk = Coin.value(10_000, 'USD')  # $100.00 for a pack of 6
alloc = bulk.allocate(per: 6, rounding_policy: :ceil)
alloc.value          # => Coin($16.67)
alloc.price(qty: 2)  # => Coin($33.34)

Since:

  • 0.1.0

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(coin:, divisor:, rounding_policy:) ⇒ Allocation

Returns a new instance of Allocation.

Parameters:

  • coin (Coin)

    the total monetary value

  • divisor (Numeric)

    the number of units; must be positive

  • rounding_policy (Symbol)

    one of WhittakerTech::Midas::ROUNDING_POLICIES

Raises:

  • (TypeError)

    if coin is not a Coin

  • (TypeError)

    if divisor is not a positive Numeric

  • (ArgumentError)

    if rounding_policy is not a recognised policy key

Since:

  • 0.1.0



64
65
66
67
68
69
70
71
72
# File 'app/models/whittaker_tech/midas/coin/allocation.rb', line 64

def initialize(coin:, divisor:, rounding_policy:)
  raise TypeError unless coin.is_a?(WhittakerTech::Midas::Coin)
  raise TypeError unless divisor.is_a?(Numeric) && divisor.positive?
  raise ArgumentError unless WhittakerTech::Midas::ROUNDING_POLICIES.key?(rounding_policy)

  @coin = coin.freeze
  @divisor = divisor
  @rounding_policy = rounding_policy.freeze
end

Instance Attribute Details

#coinCoin (readonly)

Returns the total monetary amount this Allocation is based on.

Returns:

  • (Coin)

    the total monetary amount this Allocation is based on

Since:

  • 0.1.0



36
37
38
# File 'app/models/whittaker_tech/midas/coin/allocation.rb', line 36

def coin
  @coin
end

#divisorNumeric (readonly) Also known as: per

Returns the number of units the coin covers (the divisor).

Returns:

  • (Numeric)

    the number of units the coin covers (the divisor)

Since:

  • 0.1.0



39
40
41
# File 'app/models/whittaker_tech/midas/coin/allocation.rb', line 39

def divisor
  @divisor
end

#rounding_policySymbol (readonly)

Returns the rounding policy applied to per-unit calculations.

Returns:

  • (Symbol)

    the rounding policy applied to per-unit calculations

Since:

  • 0.1.0



42
43
44
# File 'app/models/whittaker_tech/midas/coin/allocation.rb', line 42

def rounding_policy
  @rounding_policy
end

Instance Method Details

#inspectString

Human-readable representation for logs and console output.

Returns:

  • (String)

Since:

  • 0.1.0



108
109
110
# File 'app/models/whittaker_tech/midas/coin/allocation.rb', line 108

def inspect
  "#<#{self.class.name} coin=#{coin.inspect} divisor=#{divisor} rounding_policy=#{rounding_policy}>"
end

#price(qty: 1) ⇒ Coin

Returns the total price for a given quantity of units.

Uses (total_minor * qty) / divisor rounded via #rounding_policy. Equivalent to qty units at the per-unit rate, but computed from the original total to minimise accumulated rounding error.

Examples:

alloc = Coin.value(10_000, 'USD').allocate(per: 6)
alloc.price(qty: 3)   # => Coin($50.00)  (10000 * 3 / 6 = 5000 cents)

Parameters:

  • qty (Numeric) (defaults to: 1)

    the number of units; must be >= 0

Returns:

Raises:

  • (ArgumentError)

    if qty is negative or not Numeric

Since:

  • 0.1.0



97
98
99
100
101
102
103
104
# File 'app/models/whittaker_tech/midas/coin/allocation.rb', line 97

def price(qty: 1)
  raise ArgumentError unless qty.is_a?(Numeric) && qty >= 0

  WhittakerTech::Midas::Coin.value(
    round((coin.currency_minor * qty) / divisor),
    coin.currency_code
  ).freeze
end

#valueCoin

Returns the per-unit Coin after dividing by #divisor and applying the #rounding_policy.

The result is still a Coin — payable money representing a single unit.

Returns:

Since:

  • 0.1.0



80
81
82
# File 'app/models/whittaker_tech/midas/coin/allocation.rb', line 80

def value
  coin.divide(divisor, rounding_policy: rounding_policy)
end