Class: RVGP::Journal::Commodity
- Inherits:
-
Object
- Object
- RVGP::Journal::Commodity
- Defined in:
- lib/rvgp/journal/commodity.rb
Overview
This abstraction defines a simple commodity entry, as would be found in a pta journal. Such commodities can appear in the form of currency, such as ‘$ 1.30’ or in any other format that hledger and ledger parse. ie ‘1 HOUSE’.
There’s a lot of additional functionality provided by this class, including math related helper functions.
NOTE: the easiest way to create a commodity in your code, is by way of the provided String#to_commodity method. Such as: ‘$ 1.30’.to_commodity.
Units of a commodity are stored in int’s, with precision. This ensures that there is no potential for floating point precision errors, affecting these commodities.
A number of constants, relating to the built-in support of various currencies, are available as part of RVGP, in the form of the iso-4217-currencies.json file. Which, is loaded automatically during initialization.
Defined Under Namespace
Classes: ConversionError, UnimplementedError
Instance Attribute Summary collapse
-
#alphabetic_code ⇒ String
The ISO-4217 ‘Alphabetic Code’ of this commodity.
-
#code ⇒ String
The code of this commodity.
-
#precision ⇒ Integer
The exponent of the characteristic, which is used to separate the mantissa from the significand.
-
#quantity ⇒ Integer
The number of units, of this currency, before applying a fractional representation (Ie “$ 2.89” is stored in the form of :quantity 289).
Class Method Summary collapse
-
.from_s(str) ⇒ RVGP::Journal::Commodity
Given a string, such as “$ 20.57”, or “1 MERCEDESBENZ”, Construct and return a commodity representation.
-
.from_symbol_and_amount(symbol, amount = 0) ⇒ RVGP::Journal::Commodity
Given a code, or symbol, and a quantity - Construct and return a commodity representation.
Instance Method Summary collapse
-
#!=(rvalue) ⇒ TrueClass, FalseClass
Ensure that rvalue is a commodity.
-
#*(rvalue) ⇒ RVGP::Journal::Commodity
If the rvalue is a commodity, assert that we share the same commodity code, and if so multiple our quantity by the rvalue quantity.
-
#+(rvalue) ⇒ RVGP::Journal::Commodity
If the rvalue is a commodity, assert that we share the same commodity code, and if so sum our quantity with the rvalue quantity.
-
#-(rvalue) ⇒ RVGP::Journal::Commodity
If the rvalue is a commodity, assert that we share the same commodity code, and if so subtract the rvalue quantity from our quantity.
-
#/(rvalue) ⇒ RVGP::Journal::Commodity
If the rvalue is a commodity, assert that we share the same commodity code, and if so divide our quantity by the rvalue quantity.
-
#<(rvalue) ⇒ TrueClass, FalseClass
Ensure that rvalue is a commodity.
-
#<=(rvalue) ⇒ TrueClass, FalseClass
Ensure that rvalue is a commodity.
-
#<=>(rvalue) ⇒ Integer
Ensure that rvalue is a commodity.
-
#==(rvalue) ⇒ TrueClass, FalseClass
Ensure that rvalue is a commodity.
-
#>(rvalue) ⇒ TrueClass, FalseClass
Ensure that rvalue is a commodity.
-
#>=(rvalue) ⇒ TrueClass, FalseClass
Ensure that rvalue is a commodity.
-
#abs ⇒ RVGP::Journal::Commodity
Returns a copy of the current Commodity, with the absolute value of quanity.
-
#coerce(other) ⇒ Object
We’re mostly/only using this to support [].sum atm.
-
#floor(to_digit) ⇒ RVGP::Journal::Commodity
This method returns a new Commodity, with :floor applied to its :quantity.
-
#initialize(code, alphabetic_code, quantity, precision) ⇒ Commodity
constructor
Create a commodity, from the constituent parts.
-
#invert! ⇒ RVGP::Journal::Commodity
Multiply the quantity by -1.
-
#method_missing(attr, rvalue) ⇒ RVGP::Journal::Commodity
If an unhandled methods is encountered between ourselves, and another commodity, we dispatch that method to the quantity of self, against the quantity of the provided commodity.
-
#negative? ⇒ TrueClass, FalseClass
Returns whether or not the quantity is less than zero.
-
#positive? ⇒ TrueClass, FalseClass
Returns whether or not the quantity is greater than zero.
-
#quantity_as_bigdecimal ⇒ BigDecimal
Returns the quantity component of the commodity, as a BigDecimal.
-
#quantity_as_decimal_pair ⇒ Array<Integer>
This returns the characteristic and mantissa for our quantity, given our precision, note that we do not return the +/- signage.
-
#quantity_as_s(options = {}) ⇒ String
Render the :quantity, to a string.
- #respond_to_missing?(name, _include_private = false) ⇒ Boolean
-
#round(to_digit) ⇒ RVGP::Journal::Commodity
This method returns a new Commodity, with :round applied to its :quantity.
-
#to_f ⇒ Float
Returns the quantity component of the commodity, after being adjusted for :precision, as a Float.
-
#to_s(options = {}) ⇒ String
Render the commodity to a string, in the form it would appear in a journal.
Constructor Details
#initialize(code, alphabetic_code, quantity, precision) ⇒ Commodity
Create a commodity, from the constituent parts
69 70 71 72 73 74 |
# File 'lib/rvgp/journal/commodity.rb', line 69 def initialize(code, alphabetic_code, quantity, precision) @code = code @alphabetic_code = alphabetic_code @quantity = quantity.to_i @precision = precision.to_i end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(attr, rvalue) ⇒ RVGP::Journal::Commodity
If an unhandled methods is encountered between ourselves, and another commodity, we dispatch that method to the quantity of self, against the quantity of the provided commodity.
320 321 322 323 324 325 326 327 328 329 330 331 332 333 |
# File 'lib/rvgp/journal/commodity.rb', line 320 def method_missing(name, *args, &blk) # This handles most all of the numeric methods if @quantity.respond_to?(name) && args.length == 1 && args[0].is_a?(self.class) assert_commodity args[0] unless commodity.precision == precision raise UnimplementedError, format('Unimplemented operation %s Wot do?', name.inspect) end RVGP::Journal::Commodity.new code, alphabetic_code, @quantity.send(name, args[0].quantity, &blk), precision else super end end |
Instance Attribute Details
#alphabetic_code ⇒ String
The ISO-4217 ‘Alphabetic Code’ of this commodity. This code is used for various non-rendering functions. (Equality testing, Conversion lookups…)
34 35 36 |
# File 'lib/rvgp/journal/commodity.rb', line 34 def alphabetic_code @alphabetic_code end |
#code ⇒ String
The code of this commodity. Which, may be the same as :alphabetic_code, or, may take the form of symbol. (ie ‘$’). This code is used to render the commodity to strings.
34 35 36 |
# File 'lib/rvgp/journal/commodity.rb', line 34 def code @code end |
#precision ⇒ Integer
The exponent of the characteristic, which is used to separate the mantissa from the significand.
34 35 36 |
# File 'lib/rvgp/journal/commodity.rb', line 34 def precision @precision end |
#quantity ⇒ Integer
The number of units, of this currency, before applying a fractional representation (Ie “$ 2.89” is stored in the form of :quantity 289)
34 35 36 |
# File 'lib/rvgp/journal/commodity.rb', line 34 def quantity @quantity end |
Class Method Details
.from_s(str) ⇒ RVGP::Journal::Commodity
Given a string, such as “$ 20.57”, or “1 MERCEDESBENZ”, Construct and return a commodity representation
338 339 340 |
# File 'lib/rvgp/journal/commodity.rb', line 338 def self.from_s(str) commodity_parts_from_string str end |
.from_symbol_and_amount(symbol, amount = 0) ⇒ RVGP::Journal::Commodity
Given a code, or symbol, and a quantity - Construct and return a commodity representation.
357 358 359 360 361 362 363 364 365 366 367 368 369 370 |
# File 'lib/rvgp/journal/commodity.rb', line 357 def self.from_symbol_and_amount(symbol, amount = 0) currency = RVGP::Journal::Currency.from_code_or_symbol symbol precision, quantity = *precision_and_quantity_from_amount(amount) # NOTE: Sometimes (say shares) we deal with fractions of a penny. If this # is such a case, we preserve the larger precision if currency && currency.minor_unit > precision # This is a case where, say "$ 1" is passed. But, we want to store that # as 100 quantity *= 10**(currency.minor_unit - precision) precision = currency.minor_unit end new symbol, currency ? currency.alphabetic_code : symbol, quantity, precision end |
Instance Method Details
#!=(rvalue) ⇒ TrueClass, FalseClass
Ensure that rvalue is a commodity. Then return a boolean indicating whether self.quantity is not equal to rvalue’s quantity.
217 218 219 220 221 222 223 224 225 |
# File 'lib/rvgp/journal/commodity.rb', line 217 %i[> < <=> >= <= == !=].each do |operation| define_method(operation) do |rvalue| assert_commodity rvalue lquantity, rquantity = quantities_denominated_against rvalue lquantity.send operation, rquantity end end |
#*(rvalue) ⇒ RVGP::Journal::Commodity
If the rvalue is a commodity, assert that we share the same commodity code, and if so multiple our quantity by the rvalue quantity. If rvalue is numeric, multiply our quantity by this numeric.
|
# File 'lib/rvgp/journal/commodity.rb', line 227
|
#+(rvalue) ⇒ RVGP::Journal::Commodity
If the rvalue is a commodity, assert that we share the same commodity code, and if so sum our quantity with the rvalue quantity.
|
# File 'lib/rvgp/journal/commodity.rb', line 256
|
#-(rvalue) ⇒ RVGP::Journal::Commodity
If the rvalue is a commodity, assert that we share the same commodity code, and if so subtract the rvalue quantity from our quantity.
267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 |
# File 'lib/rvgp/journal/commodity.rb', line 267 %i[+ -].each do |operation| define_method(operation) do |rvalue| assert_commodity rvalue lquantity, rquantity, dprecision = quantities_denominated_against rvalue result = lquantity.send operation, rquantity # Adjust the dprecision. Probably there's a better way to do this, but, # this works our_currency = RVGP::Journal::Currency.from_code_or_symbol code # This is a special case: return RVGP::Journal::Commodity.new code, alphabetic_code, result, our_currency.minor_unit if result.zero? # If we're trying to remove more digits than minor_unit, we have to adjust # our cut if our_currency && (dprecision > our_currency.minor_unit) && /\A.+?(0+)\Z/.match(result.to_s) && ::Regexp.last_match(1) trim_length = ::Regexp.last_match(1).length dprecision -= trim_length if dprecision < our_currency.minor_unit add = our_currency.minor_unit - dprecision dprecision += add trim_length -= add end result /= 10**trim_length end RVGP::Journal::Commodity.new code, alphabetic_code, result, dprecision end end |
#/(rvalue) ⇒ RVGP::Journal::Commodity
If the rvalue is a commodity, assert that we share the same commodity code, and if so divide our quantity by the rvalue quantity. If rvalue is numeric, divide our quantity by this numeric.
240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 |
# File 'lib/rvgp/journal/commodity.rb', line 240 %i[* /].each do |operation| define_method(operation) do |rvalue| result = if rvalue.is_a? Numeric # These mul/divs are often "Divide by half" "Multiply by X" instructions # for which the rvalue is not, and should not be, a commodity. quantity_as_bigdecimal.send operation, rvalue else assert_commodity rvalue raise UnimplementedError end RVGP::Journal::Commodity.from_symbol_and_amount code, result.round(MAX_DECIMAL_DIGITS).to_s('F') end end |
#<(rvalue) ⇒ TrueClass, FalseClass
Ensure that rvalue is a commodity. Then return a boolean indicating whether self.quantity is less than rvalue’s quantity.
|
# File 'lib/rvgp/journal/commodity.rb', line 182
|
#<=(rvalue) ⇒ TrueClass, FalseClass
Ensure that rvalue is a commodity. Then return a boolean indicating whether self.quantity is less than or equal to rvalue’s quantity.
|
# File 'lib/rvgp/journal/commodity.rb', line 200
|
#<=>(rvalue) ⇒ Integer
Ensure that rvalue is a commodity. Then returns an integer indicating whether self.quantity is (spaceship) rvalue’s quantity. More specifically: -1 on <, 0 on ==, 1 on >.
|
# File 'lib/rvgp/journal/commodity.rb', line 188
|
#==(rvalue) ⇒ TrueClass, FalseClass
Ensure that rvalue is a commodity. Then return a boolean indicating whether self.quantity is equal to rvalue’s quantity.
|
# File 'lib/rvgp/journal/commodity.rb', line 206
|
#>(rvalue) ⇒ TrueClass, FalseClass
Ensure that rvalue is a commodity. Then return a boolean indicating whether self.quantity is greater than rvalue’s quantity.
|
# File 'lib/rvgp/journal/commodity.rb', line 176
|
#>=(rvalue) ⇒ TrueClass, FalseClass
Ensure that rvalue is a commodity. Then return a boolean indicating whether self.quantity is greater than or equal to rvalue’s quantity.
|
# File 'lib/rvgp/journal/commodity.rb', line 194
|
#abs ⇒ RVGP::Journal::Commodity
Returns a copy of the current Commodity, with the absolute value of quanity.
158 159 160 |
# File 'lib/rvgp/journal/commodity.rb', line 158 def abs RVGP::Journal::Commodity.new code, alphabetic_code, quantity.abs, precision end |
#coerce(other) ⇒ Object
We’re mostly/only using this to support [].sum atm
303 304 305 306 307 |
# File 'lib/rvgp/journal/commodity.rb', line 303 def coerce(other) super unless other.is_a? Integer [RVGP::Journal::Commodity.new(code, alphabetic_code, other, precision), self] end |
#floor(to_digit) ⇒ RVGP::Journal::Commodity
This method returns a new Commodity, with :floor applied to its :quantity.
165 166 167 |
# File 'lib/rvgp/journal/commodity.rb', line 165 def floor(to_digit) round_or_floor to_digit, :floor end |
#invert! ⇒ RVGP::Journal::Commodity
Multiply the quantity by -1. This mutates the state of self.
151 152 153 154 |
# File 'lib/rvgp/journal/commodity.rb', line 151 def invert! @quantity *= -1 self end |
#negative? ⇒ TrueClass, FalseClass
Returns whether or not the quantity is less than zero.
145 146 147 |
# File 'lib/rvgp/journal/commodity.rb', line 145 def negative? quantity.negative? end |
#positive? ⇒ TrueClass, FalseClass
Returns whether or not the quantity is greater than zero.
139 140 141 |
# File 'lib/rvgp/journal/commodity.rb', line 139 def positive? quantity.positive? end |
#quantity_as_bigdecimal ⇒ BigDecimal
Returns the quantity component of the commodity, as a BigDecimal
101 102 103 |
# File 'lib/rvgp/journal/commodity.rb', line 101 def quantity_as_bigdecimal BigDecimal quantity_as_s end |
#quantity_as_decimal_pair ⇒ Array<Integer>
This returns the characteristic and mantissa for our quantity, given our precision, note that we do not return the +/- signage. That information is destroyed here
108 109 110 111 |
# File 'lib/rvgp/journal/commodity.rb', line 108 def quantity_as_decimal_pair characteristic = quantity.abs.to_i / (10**precision) [characteristic, quantity.abs.to_i - (characteristic * (10**precision))] end |
#quantity_as_s(options = {}) ⇒ String
Render the :quantity, to a string. This is output without code notation, and merely expressed the quantity with the expected symbols (commas, periods) .
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/rvgp/journal/commodity.rb', line 83 def quantity_as_s( = {}) characteristic, mantissa = if .key? :precision round([:precision]).quantity_as_decimal_pair else quantity_as_decimal_pair end characteristic = characteristic.to_s to_precision = [:precision] || precision mantissa = to_precision.positive? ? format("%0#{to_precision}d", mantissa) : nil characteristic = characteristic.reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse if [:commatize] [negative? ? '-' : nil, characteristic, mantissa ? '.' : nil, mantissa].compact.join end |
#respond_to_missing?(name, _include_private = false) ⇒ Boolean
309 310 311 |
# File 'lib/rvgp/journal/commodity.rb', line 309 def respond_to_missing?(name, _include_private = false) @quantity.respond_to? name end |
#round(to_digit) ⇒ RVGP::Journal::Commodity
This method returns a new Commodity, with :round applied to its :quantity.
172 173 174 |
# File 'lib/rvgp/journal/commodity.rb', line 172 def round(to_digit) round_or_floor to_digit, :round end |
#to_f ⇒ Float
Returns the quantity component of the commodity, after being adjusted for :precision, as a Float. Consider using #quantity_as_bigdecimal instead.
133 134 135 |
# File 'lib/rvgp/journal/commodity.rb', line 133 def to_f quantity_as_s.to_f end |
#to_s(options = {}) ⇒ String
Render the commodity to a string, in the form it would appear in a journal. This output includes the commodity code, as well as a period and, optionally commas.
121 122 123 124 125 126 127 128 |
# File 'lib/rvgp/journal/commodity.rb', line 121 def to_s( = {}) ret = [quantity_as_s()] if code && ![:no_code] operand = code.count(' ').positive? ? ['"', code, '"'].join : code code.length == 1 ? ret.unshift(operand) : ret.push(operand) end ret.join(' ') end |