Class: WhittakerTech::Midas::Coin
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- WhittakerTech::Midas::Coin
- Includes:
- Poly::Joins, Poly::Owners, Poly::Role, Arithmetic, Bidi, Converter, Presenter
- Defined in:
- app/models/whittaker_tech/midas/coin.rb
Overview
Coin is the canonical, persisted representation of a monetary value.
Conceptual model
- A Coin represents payable money: an exact integer count of minor units (cents, pence, etc.) in a specific currency.
- A Coin is immutable by convention: arithmetic operations return new frozen Coins rather than mutating the receiver.
- A Coin owns arithmetic truth — not presentation, pricing interpretation, or currency conversion.
Storage
Every Coin is persisted in midas_coins and belongs polymorphically to
any domain object (resource_type / resource_id). The resource_role
distinguishes multiple coins on the same resource (e.g. "price",
"cost", "tax").
Modules
Behavior is composed via mixins:
+------------+----------------------------------------------+
| Module | Responsibility |
+------------+----------------------------------------------+
| Arithmetic | +, -, *, /, %, negate, equality |
| Bidi | Unicode bidirectional text isolation |
| Converter | Currency conversion (live, audited via Exchange) |
| Presenter | Token-based formatting grammar |
+------------+----------------------------------------------+
Usage via Bankable
The typical entry point is the WhittakerTech::Midas::Bankable concern; direct Coin construction
is mostly used in service objects and tests.
product.set_price(amount: 29.99, currency_code: 'USD')
product.price # => #<WhittakerTech::Midas::Coin ...>
product.price_amount # => #<Money @fractional=2999 @currency="USD">
See also:
WhittakerTech::Midas::BankableWhittakerTech::Midas::Coin::ArithmeticWhittakerTech::Midas::Coin::Allocation
Defined Under Namespace
Modules: Arithmetic, Bidi, Converter, Presenter Classes: Allocation, Parser
Constant Summary
Constants included from Presenter
Constants included from Bidi
Bidi::LRI, Bidi::PDI, Bidi::RLI
Class Method Summary collapse
-
.parse(value, currency_code: nil) ⇒ Coin
Parses a heterogeneous input into a Coin using
Coin::Parser. -
.value(currency_minor, currency_code) ⇒ Coin
Constructs a Coin directly from an integer minor-unit count and an ISO currency code.
-
.zero(currency_code) ⇒ Coin
Returns a zero-valued Coin in the given currency.
Instance Method Summary collapse
-
#allocate(per:, rounding_policy: WhittakerTech::Midas::DEFAULT_ROUNDING_POLICY) ⇒ Coin::Allocation
Constructs a
Coin::Allocationthat interprets this Coin as a per-unit price. -
#amount ⇒ Money
Returns a Money object representing the stored monetary value.
-
#amount=(value) ⇒ void
Sets the coin's monetary value from a Money object or raw minor-unit integer.
-
#currency ⇒ String
The ISO currency code (alias for
#currency_code). -
#currency_code=(value) ⇒ void
Clears the memoized Money projection when the currency code changes.
-
#currency_minor=(value) ⇒ void
Clears the memoized Money projection when the minor-unit value changes.
-
#decimals ⇒ Integer
Returns the number of decimal places defined by the currency specification.
-
#format(to: nil) ⇒ String
Formats the Coin for display, optionally converting to another currency first.
-
#fractional ⇒ Integer
The raw minor-unit count (alias for
#currency_minor). -
#major ⇒ BigDecimal
Returns the major-unit value as a precise BigDecimal.
-
#minor ⇒ Integer
The raw minor-unit count (alias for
#currency_minor). -
#scale ⇒ Numeric
Returns the scaling factor used to convert major units to minor units.
Methods included from Presenter
build_context, dispatch, format, #present, scan, token_approx, token_currency_code, token_currency_symbol, token_major, token_minor, token_number_only, token_per_exact, token_percent, token_total, token_units
Methods included from Converter
Methods included from Bidi
#bidi_currency_dir, #bidi_isolate, #bidi_isolate_number
Methods included from Arithmetic
#%, #*, #+, #-, #-@, #==, #divide, #hash, #negate
Class Method Details
.parse(value, currency_code: nil) ⇒ Coin
Parses a heterogeneous input into a Coin using Coin::Parser.
Accepted input types: Coin, Money, Numeric, String.
275 276 277 |
# File 'app/models/whittaker_tech/midas/coin.rb', line 275 def parse(value, currency_code: nil) Parser.parse(value, currency_code:) end |
.value(currency_minor, currency_code) ⇒ Coin
Constructs a Coin directly from an integer minor-unit count and an ISO currency code.
This is the lowest-level factory. It enforces that currency_minor is
an Integer (fractional minor units are not permitted).
244 245 246 247 248 |
# File 'app/models/whittaker_tech/midas/coin.rb', line 244 def value(currency_minor, currency_code) raise TypeError unless currency_minor.is_a?(Integer) new(currency_minor:, currency_code:) end |
.zero(currency_code) ⇒ Coin
Returns a zero-valued Coin in the given currency.
257 258 259 |
# File 'app/models/whittaker_tech/midas/coin.rb', line 257 def zero(currency_code) new(currency_minor: 0, currency_code:) end |
Instance Method Details
#allocate(per:, rounding_policy: WhittakerTech::Midas::DEFAULT_ROUNDING_POLICY) ⇒ Coin::Allocation
Constructs a Coin::Allocation that interprets this Coin as a per-unit price.
The Coin itself is unchanged; Coin::Allocation encapsulates the
per-unit interpretation and rounding policy.
188 189 190 191 192 193 194 |
# File 'app/models/whittaker_tech/midas/coin.rb', line 188 def allocate(per:, rounding_policy: WhittakerTech::Midas::DEFAULT_ROUNDING_POLICY) WhittakerTech::Midas::Coin::Allocation.new( coin: self, divisor: per, rounding_policy: ) end |
#amount ⇒ Money
Returns a Money object representing the stored monetary value.
This is a projection, not canonical value. Use #currency_minor
and #currency_code as the source of truth.
Memoized for performance. The memo is cleared automatically when
#currency_minor= or #currency_code= are called.
97 98 99 |
# File 'app/models/whittaker_tech/midas/coin.rb', line 97 def amount @amount ||= Money.new(currency_minor, currency_code) end |
#amount=(value) ⇒ void
This method returns an undefined value.
Sets the coin's monetary value from a Money object or raw minor-unit integer.
110 111 112 113 114 115 116 117 118 119 120 121 122 |
# File 'app/models/whittaker_tech/midas/coin.rb', line 110 def amount=(value) case value when Money self.currency_minor = value.cents self.currency_code = value.currency.iso_code when Numeric raise ArgumentError, 'currency_code required before setting numeric amount' if currency_code.blank? self.currency_minor = Integer(value) else raise ArgumentError, "Invalid value for Coin#amount: #{value.inspect}" end end |
#currency ⇒ String
Returns the ISO currency code (alias for #currency_code).
149 150 151 |
# File 'app/models/whittaker_tech/midas/coin.rb', line 149 def currency currency_code end |
#currency_code=(value) ⇒ void
This method returns an undefined value.
Clears the memoized Money projection when the currency code changes.
169 170 171 172 |
# File 'app/models/whittaker_tech/midas/coin.rb', line 169 def currency_code=(value) @amount = nil super end |
#currency_minor=(value) ⇒ void
This method returns an undefined value.
Clears the memoized Money projection when the minor-unit value changes.
161 162 163 164 |
# File 'app/models/whittaker_tech/midas/coin.rb', line 161 def currency_minor=(value) @amount = nil super end |
#decimals ⇒ Integer
Returns the number of decimal places defined by the currency specification.
For example, USD = 2, JPY = 0, BHD = 3 This is informational and does not imply rounding or formatting.
202 203 204 |
# File 'app/models/whittaker_tech/midas/coin.rb', line 202 def decimals Money::Currency.new(currency_code).decimal_places end |
#format(to: nil) ⇒ String
When to is given, this performs a live conversion (via
#convert_to) and writes an Exchange audit row on every call. If
formatting the same converted value repeatedly (e.g. in a view loop),
convert once and reuse the result's #amount.format instead.
Formats the Coin for display, optionally converting to another currency first.
This is a convenience wrapper around the Money gem's #format. For
richer formatting use #present with a pattern string.
137 138 139 140 141 |
# File 'app/models/whittaker_tech/midas/coin.rb', line 137 def format(to: nil) return amount.format unless to convert_to(to).amount.format end |
#fractional ⇒ Integer
Returns the raw minor-unit count (alias for #currency_minor).
154 155 156 |
# File 'app/models/whittaker_tech/midas/coin.rb', line 154 def fractional currency_minor end |
#major ⇒ BigDecimal
Returns the major-unit value as a precise BigDecimal.
This is NOT payable money. It is intended for inspection and formatting only. No rounding policy is applied.
225 226 227 |
# File 'app/models/whittaker_tech/midas/coin.rb', line 225 def major BigDecimal(currency_minor) / scale end |
#minor ⇒ Integer
Returns the raw minor-unit count (alias for #currency_minor).
144 145 146 |
# File 'app/models/whittaker_tech/midas/coin.rb', line 144 def minor currency_minor end |
#scale ⇒ Numeric
Returns the scaling factor used to convert major units to minor units.
Equivalent to 10 ** decimals. For USD this is 100; for JPY it is 1.
211 212 213 |
# File 'app/models/whittaker_tech/midas/coin.rb', line 211 def scale 10**decimals end |