latinum 1.4.2 → 1.7.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Copyright, 2015, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
4
  #
3
5
  # Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -21,46 +23,84 @@
21
23
  require 'bigdecimal'
22
24
  require 'bigdecimal/util'
23
25
 
26
+ require_relative 'error'
27
+
24
28
  module Latinum
25
- class DifferentResourceNameError < ArgumentError
26
- def initialize
27
- super "Cannot operate on different currencies!"
28
- end
29
- end
30
-
31
- # A fixed unit in a given named resource
29
+ # A Resource represents a fixed amount of a named currency or material.
32
30
  class Resource
33
- include Comparable
31
+ # Parse a string representation of a resource.
32
+ # @parameter string [String] e.g "5 NZD".
33
+ # @returns [Resource] The Resource that represents the parsed string.
34
+ def self.parse(string, default_name: nil)
35
+ amount, name = string.split(/\s+/, 2)
36
+
37
+ self.new(amount, name || default_name)
38
+ end
34
39
 
35
- attr :amount
36
- attr :name
40
+ # Load a string representation of a resource.
41
+ # @parameter string [String | Nil] e.g. "5 NZD" or nil.
42
+ # @returns [Resource | Nil] The Resource that represents the parsed string.
43
+ def self.load(string)
44
+ if string
45
+ # Remove any whitespaces
46
+ string = string.strip
47
+
48
+ parse(string) unless string.empty?
49
+ end
50
+ end
51
+
52
+ # Dump a string representation of a resource.
53
+ # @parameter resource [Resource] The resource to dump.
54
+ # @returns [String | Nil] A string that represents the {Resource}.
55
+ def self.dump(resource)
56
+ resource.to_s if resource
57
+ end
58
+
59
+ include Comparable
37
60
 
38
61
  def initialize(amount, name)
39
62
  @amount = amount.to_d
40
63
  @name = name
41
64
  end
42
65
 
43
- # By default, we can only add and subtract if the name is the same
66
+ # The amount of the resource.
67
+ # @attribute [BigDecimal]
68
+ attr :amount
69
+
70
+ # The name of the resource.
71
+ # @attribute [String]
72
+ attr :name
73
+
74
+ # Add two resources. Must have the same name.
75
+ # @returns [Resource] A resource with the added amount.
44
76
  def + other
45
77
  raise DifferentResourceNameError if @name != other.name
46
78
 
47
79
  self.class.new(@amount + other.amount, @name)
48
80
  end
49
81
 
82
+ # Subtract two resources. Must have the same name.
83
+ # @returns [Resource] A resource with the subtracted amount.
50
84
  def - other
51
85
  raise DifferentResourceNameError if @name != other.name
52
86
 
53
87
  self.class.new(@amount - other.amount, @name)
54
88
  end
55
89
 
90
+ # Invert the amount of the resource.
91
+ # @returns [Resource] A resource with the negated amount.
56
92
  def -@
57
93
  self.class.new(-@amount, @name)
58
94
  end
59
95
 
96
+ # Multiplies the resource by a given factor.
97
+ # @returns [Resource] A resource with the updated amount.
60
98
  def * factor
61
99
  self.class.new(@amount * factor, @name)
62
100
  end
63
101
 
102
+ # Divides the resource by a given factor.
103
+ # @returns [Resource] A resource with the updated amount.
64
104
  def / factor
65
105
  if factor.is_a? self.class
66
106
  raise DifferentResourceNameError if @name != factor.name
@@ -71,6 +111,10 @@ module Latinum
71
111
  end
72
112
  end
73
113
 
114
+ # Compute a new resource using the given exchange rate for the specified name.
115
+ # @parameter rate [Numeric] The exchange rate to use.
116
+ # @parameter name [String] The name of the new resource.
117
+ # @parameter precision [Integer | Nil] The number of decimal places to round to.
74
118
  def exchange(rate, name, precision = nil)
75
119
  return self if @name == name
76
120
 
@@ -81,20 +125,28 @@ module Latinum
81
125
  self.class.new(exchanged_amount, name)
82
126
  end
83
127
 
128
+ # A human readable string representation of the resource amount and name.
129
+ # @returns [String] e.g. "5 NZD".
84
130
  def to_s
85
131
  "#{@amount.to_s('F')} #{@name}"
86
132
  end
87
133
 
134
+ # A human readable string representation of the resource amount.
135
+ # @returns [String] e.g. "5.00".
136
+ def to_digits
137
+ @amount.to_s('F')
138
+ end
139
+
88
140
  def inspect
89
141
  "#<#{self.class.name} #{self.to_s.dump}>"
90
142
  end
91
143
 
92
- # Compare with another resource or a Numeric value.
144
+ # Compare with another {Resource} or a Numeric value.
93
145
  def <=> other
94
146
  if other.is_a? self.class
95
147
  result = @amount <=> other.amount
96
148
  return result unless result == 0
97
-
149
+
98
150
  result = @name <=> other.name
99
151
  return result
100
152
  elsif other.is_a? Numeric
@@ -107,30 +159,11 @@ module Latinum
107
159
  end
108
160
 
109
161
  def eql? other
110
- self.class.eql? other.class and @name.eql? other.name and @amount.eql? other.amount
111
- end
112
-
113
- class << self
114
- def parse(string, default_name: nil)
115
- amount, name = string.split(/\s+/, 2)
116
-
117
- self.new(amount, name || default_name)
118
- end
119
-
120
- def load(string)
121
- if string
122
- # Remove any whitespaces
123
- string = string.strip
124
-
125
- parse(string) unless string.empty?
126
- end
127
- end
128
-
129
- def dump(resource)
130
- resource.to_s if resource
131
- end
162
+ self.class.eql?(other.class) and @name.eql?(other.name) and @amount.eql?(other.amount)
132
163
  end
133
164
 
165
+ # Whether the amount of the resource is zero.
166
+ # @returns [Boolean]
134
167
  def zero?
135
168
  @amount.zero?
136
169
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Copyright, 2015, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
4
  #
3
5
  # Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -19,5 +21,5 @@
19
21
  # THE SOFTWARE.
20
22
 
21
23
  module Latinum
22
- VERSION = "1.4.2"
24
+ VERSION = "1.7.1"
23
25
  end
metadata CHANGED
@@ -1,17 +1,17 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: latinum
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.2
4
+ version: 1.7.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-11-20 00:00:00.000000000 Z
11
+ date: 2021-08-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: covered
14
+ name: bundler
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - ">="
@@ -25,7 +25,7 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
- name: bundler
28
+ name: covered
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
@@ -39,65 +39,53 @@ dependencies:
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
- name: rspec
42
+ name: rake
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - "~>"
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: '3.4'
47
+ version: '0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - "~>"
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
- version: '3.4'
54
+ version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
- name: rake
56
+ name: rspec
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - ">="
59
+ - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '0'
61
+ version: '3.4'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - ">="
66
+ - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '0'
69
- description:
68
+ version: '3.4'
69
+ description:
70
70
  email:
71
- - samuel.williams@oriontransfer.co.nz
72
71
  executables: []
73
72
  extensions: []
74
73
  extra_rdoc_files: []
75
74
  files:
76
- - ".rspec"
77
- - ".travis.yml"
78
- - Gemfile
79
- - README.md
80
- - Rakefile
81
- - latinum.gemspec
82
75
  - lib/latinum.rb
83
76
  - lib/latinum/bank.rb
84
77
  - lib/latinum/collection.rb
85
78
  - lib/latinum/currencies/global.rb
79
+ - lib/latinum/error.rb
86
80
  - lib/latinum/formatters.rb
87
81
  - lib/latinum/resource.rb
88
82
  - lib/latinum/version.rb
89
- - spec/latinum/bank_spec.rb
90
- - spec/latinum/collection_spec.rb
91
- - spec/latinum/comparison_spec.rb
92
- - spec/latinum/formatters_spec.rb
93
- - spec/latinum/integrals_spec.rb
94
- - spec/latinum/resource_spec.rb
95
- - spec/spec_helper.rb
96
83
  homepage: https://github.com/ioquatix/latinum
97
84
  licenses:
98
85
  - MIT
99
- metadata: {}
100
- post_install_message:
86
+ metadata:
87
+ funding_uri: https://github.com/sponsors/ioquatix/
88
+ post_install_message:
101
89
  rdoc_options: []
102
90
  require_paths:
103
91
  - lib
@@ -105,23 +93,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
105
93
  requirements:
106
94
  - - ">="
107
95
  - !ruby/object:Gem::Version
108
- version: '0'
96
+ version: '2.5'
109
97
  required_rubygems_version: !ruby/object:Gem::Requirement
110
98
  requirements:
111
99
  - - ">="
112
100
  - !ruby/object:Gem::Version
113
101
  version: '0'
114
102
  requirements: []
115
- rubygems_version: 3.0.6
116
- signing_key:
103
+ rubygems_version: 3.1.6
104
+ signing_key:
117
105
  specification_version: 4
118
- summary: Latinum is a simple gem for managing resource computations, including money
119
- and minerals.
120
- test_files:
121
- - spec/latinum/bank_spec.rb
122
- - spec/latinum/collection_spec.rb
123
- - spec/latinum/comparison_spec.rb
124
- - spec/latinum/formatters_spec.rb
125
- - spec/latinum/integrals_spec.rb
126
- - spec/latinum/resource_spec.rb
127
- - spec/spec_helper.rb
106
+ summary: Provides immutable resource and money computations.
107
+ test_files: []
data/.rspec DELETED
@@ -1,3 +0,0 @@
1
- --format documentation
2
- --warnings
3
- --require spec_helper
data/.travis.yml DELETED
@@ -1,20 +0,0 @@
1
- language: ruby
2
- dist: xenial
3
- cache: bundler
4
-
5
- matrix:
6
- include:
7
- - rvm: 2.3
8
- - rvm: 2.4
9
- - rvm: 2.5
10
- - rvm: 2.6
11
- - rvm: 2.6
12
- os: osx
13
- - rvm: 2.6
14
- env: COVERAGE=BriefSummary,Coveralls
15
- - rvm: truffleruby
16
- - rvm: jruby-head
17
- - rvm: ruby-head
18
- allow_failures:
19
- - rvm: ruby-head
20
- - rvm: jruby-head
data/Gemfile DELETED
@@ -1,3 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- gemspec
data/README.md DELETED
@@ -1,276 +0,0 @@
1
- # Latinum
2
-
3
- Latinum is a library for resource and currency calculations. It provides immutable `Resource` objects for dealing with quantities of named resources with an arbitrary number of decimal places, and `Bank` objects for converting resources and formatting them for output. Latinum doesn't include any global state by default and thus is ideal for integration with other frameworks/libraries.
4
-
5
- [![Build Status](https://travis-ci.com/ioquatix/latinum.svg?branch=master)](https://travis-ci.com/ioquatix/latinum)
6
- [![Code Climate](https://codeclimate.com/github/ioquatix/latinum.svg)](https://codeclimate.com/github/ioquatix/latinum)
7
- [![Coverage Status](https://coveralls.io/repos/ioquatix/latinum/badge.svg)](https://coveralls.io/r/ioquatix/latinum)
8
-
9
- ## Motivation
10
-
11
- I was originally planning on using the [Money gem](https://github.com/RubyMoney/money), but it's [dependency on global state](https://github.com/RubyMoney/money/blob/39b617cca8f02c885cc8246e0aab3e9dc5f90e15/lib/money/currency.rb#L19-L21) makes it hard to use if you want to deal with money as an immutable value type.
12
-
13
- Additionally, I wanted to support BitCoin, Japanese Yen, etc. The money gem was heavily biased towards decimal currency. It had (~2012) [fields like `dollars` and `cents`](https://github.com/RubyMoney/money/issues/197) which don't really make sense and don't really align with the real world. These days they have fixed parts of the API, but it's a bit of a mess now, supporting both decimal and non-decimal values.
14
-
15
- Another problem I had at the time was the [concept of zero](https://github.com/RubyMoney/money/issues/195). It should be possible to have an additive (e.g. 0) and multiplicative identity (e.g. 1) do the right thing. In fact, in Latinum, you can multiply `Latinum::Resource` instances by a scalar and get a useful result (e.g. for computing discounts).
16
-
17
- Finally, because of the above problem, it was not obvious at the time how to sum up a collection of money instances correctly. In fact, this is still a problem and a separate gem, based on the `Latinum::Collection` concept, [was made](https://github.com/lulalala/money-collection). However, this all fits together in a rather haphazard way.
18
-
19
- Latinum addresses all these issues. It has an immutable value type `Latinum::Resource` which has a robust definition: A value (e.g. 5.0025) and a resource name (USD). The semantics of resources are well defined without the need for "Currency" state like the symbol, how many decimal places, etc. So, it suits well for serialization into a database, and for formatting to the user, there is `Latinum::Bank` which gives you the choice of how you decide to format things or exchange them, whether you want to round something off, etc.
20
-
21
- ## Installation
22
-
23
- Add this line to your application's Gemfile:
24
-
25
- gem 'latinum'
26
-
27
- And then execute:
28
-
29
- $ bundle
30
-
31
- Or install it yourself as:
32
-
33
- $ gem install latinum
34
-
35
- ## Usage
36
-
37
- Latinum has several core concepts:
38
-
39
- - A `Resource` represents an immutable value with a specific face name (e.g. `'USD'`).
40
- - A `Resource` can only be combined with resources with the same face name.
41
- - A `Bank` is responsible for managing currencies and formatting options.
42
- - A `Bank` can exchange currencies explicitly with a given set of exchange rates.
43
- - A `Collection` is responsible for adding currencies together and is completely deterministic.
44
-
45
- ### Resources and Collections
46
-
47
- To create a new resource, use a string for accuracy:
48
-
49
- > ten = Latinum::Resource.new("10.00", "NZD")
50
- => 10.0 NZD
51
- > ten.amount == "10.00".to_d
52
- => true
53
-
54
- You can add resources of different values but with the same name:
55
-
56
- > ten + ten
57
- => 20.0 NZD
58
-
59
- But, you can't add resources of different names together:
60
-
61
- > twenty = Latinum::Resource.new("20.00", "AUD")
62
- => 20.0 AUD
63
- > ten + twenty
64
- DifferentResourceNameError: Cannot operate on different currencies!
65
-
66
- To add multiple currencies together, use a collection:
67
-
68
- > collection = Latinum::Collection.new
69
- > collection << [ten, twenty]
70
- > collection.collect(&:to_s)
71
- => [10.0 NZD, 20.0 AUD]
72
-
73
- #### Calculating Totals
74
-
75
- The `Latinum::Collection` is the correct way to sum up a list of transactions or other items with an
76
- associated `Latinum::Resource`. Here is an example:
77
-
78
- <table class="listing transactions" data-model="Transaction">
79
- <thead>
80
- <tr>
81
- <th class="name">Name</th>
82
- <th class="date">Date</th>
83
- <th class="price">Price</th>
84
- <th class="quantity">Quantity</th>
85
- <th class="subtotal">Sub-total</th>
86
- <th class="tax_rate">Tax</th>
87
- <th class="total">Total</th>
88
- </tr>
89
- </thead>
90
- <tbody>
91
- <?r
92
- currencies = Set.new
93
-
94
- summary = {
95
- :subtotal => Latinum::Collection.new(currencies),
96
- :tax => Latinum::Collection.new(currencies),
97
- :total => Latinum::Collection.new(currencies)
98
- }
99
-
100
- invoice.transactions.each do |transaction|
101
- subtotal = transaction.subtotal
102
- summary[:subtotal] << subtotal
103
- summary[:tax] << subtotal * transaction.tax_rate.to_d
104
- summary[:total] << transaction.total
105
-
106
- ?>
107
- <tr data-id="#{transaction.id}" data-rev="#{transaction.rev}">
108
- <th class="name">#{f.text transaction.name}</th>
109
- <td class="date">#{f.text transaction.date}</td>
110
- <td class="price">#{f.text transaction.price}</td>
111
- <td class="quantity">#{f.quantity transaction}</td>
112
- <td class="subtotal">#{f.text subtotal}</td>
113
- <td class="tax_rate">#{f.tax transaction}</td>
114
- <td class="total">#{f.text transaction.total}</td>
115
- </tr>
116
- <?r end ?>
117
- </tbody>
118
- <tfoot>
119
- <?r currencies.each do |currency| ?>
120
- <tr>
121
- <td colspan="5">#{currency} Summary:</td>
122
- <td class="subtotal">#{f.text summary[:subtotal][currency]}</td>
123
- <td class="tax_rate">#{f.text summary[:tax][currency]}</td>
124
- <td class="total">#{f.text summary[:total][currency]}</td>
125
- <td></td>
126
- </tr>
127
- <?r end ?>
128
- </tfoot>
129
- </table>
130
-
131
- ### Banks and Exchange Rates
132
-
133
- The bank is responsible for formatting and exchange rates:
134
-
135
- require 'latinum/bank'
136
- require 'latinum/currencies/global'
137
-
138
- > bank = Latinum::Bank.new(Latinum::Currencies::Global)
139
- > bank << Latinum::ExchangeRate.new("NZD", "AUD", "0.5")
140
-
141
- > nzd = Latinum::Resource.new("10", "NZD")
142
- => 10.0 NZD
143
- > aud = bank.exchange nzd, "AUD"
144
- => 5.0 AUD
145
-
146
- Formatting an amount is typically required for presentation to the end user:
147
-
148
- > bank.format(nzd)
149
- => "$10.00 NZD"
150
-
151
- > bank.format(aud, :format => :compact)
152
- => "$5.00"
153
-
154
- The bank can also be used to parse currency, which will depend on the priority of currencies if a symbol that matches multiple currencies is supplied:
155
-
156
- > bank.parse("$5")
157
- => 5.0 USD
158
-
159
- > bank.parse("€5")
160
- => 5.0 EUR
161
-
162
- Currency codes take priority over symbols if specified:
163
-
164
- > bank.parse("€5 NZD")
165
- => 5.0 NZD
166
-
167
- ### Conversion To and From Integers
168
-
169
- For storage in traditional databases, you may prefer to use integers. Based on the precision of the currency, you can store integer representations:
170
-
171
- > resource = Latinum::Resource.new("1.12345678", "BTC")
172
-
173
- > 112345678 == bank.to_integral(resource)
174
- true
175
-
176
- > resource == bank.from_integral(112345678, "BTC")
177
- true
178
-
179
- As BitCoin has 8 decimal places, it requires an integer representation with at least 10^8.
180
-
181
- ### ActiveRecord Serialization
182
-
183
- Latinum can be easily used in a [ActiveRecord](https://github.com/rails/rails/tree/master/activerecord) model simply by declaring a serialized data-type for a string or text column, e.g.
184
-
185
- class Transaction < ActiveRecord::Base
186
- serialize :total, Latinum::Resource
187
- end
188
-
189
- It can be used like so:
190
-
191
- > transaction = Transaction.new(:total => "10 NZD")
192
- > transaction.total * 2
193
- => "20.0 NZD"
194
-
195
- To format the output, use a `Latinum::Bank`, e.g. assuming the bank is set up correctly:
196
-
197
- > bank.format(transaction.total)
198
- => "$20.00 NZD"
199
-
200
- > bank.format(transaction.total, name: nil)
201
- => "$20.00"
202
-
203
- > bank.format(transaction.total, symbol: nil)
204
- => "20.00 NZD"
205
-
206
- ### Relaxo Serialization
207
-
208
- Latinum is natively supported by [Relaxo](https://github.com/ioquatix/relaxo) (CouchDB) and as such can be used in [Relaxo models](https://github.com/ioquatix/relaxo-model) easily.
209
-
210
- require 'latinum'
211
- require 'relaxo/model'
212
- require 'relaxo/model/properties/latinum'
213
-
214
- class Transaction
215
- include Relaxo::Model
216
-
217
- property :name
218
- property :price, Attribute[Latinum::Resource]
219
- end
220
-
221
- db = Relaxo.connect('test')
222
- db.create!
223
-
224
- t = Transaction.create(db, price: Latinum::Resource.load("50 NZD"))
225
-
226
- t.price
227
- # => <Latinum::Resource "50.0 NZD">
228
-
229
- # Save and reload from database server:
230
- t.save
231
- t = Transaction.fetch(db, t.id)
232
-
233
- t.price
234
- # => <Latinum::Resource "50.0 NZD">
235
-
236
- It gets stored in the database like so:
237
-
238
- {
239
- "_id": "740f4728fc9a571d826688db2f004771",
240
- "_rev": "1-45a29c63311cfa0d5a765707184b2b3b",
241
- "type": "transaction",
242
- "price": [
243
- "50.0",
244
- "NZD"
245
- ]
246
- }
247
-
248
- ## Contributing
249
-
250
- 1. Fork it
251
- 2. Create your feature branch (`git checkout -b my-new-feature`)
252
- 3. Commit your changes (`git commit -am 'Add some feature'`)
253
- 4. Push to the branch (`git push origin my-new-feature`)
254
- 5. Create new Pull Request
255
-
256
- ## License
257
-
258
- Copyright, 2015, by [Samuel G. D. Williams](http://www.codeotaku.com/samuel-williams).
259
-
260
- Permission is hereby granted, free of charge, to any person obtaining a copy
261
- of this software and associated documentation files (the "Software"), to deal
262
- in the Software without restriction, including without limitation the rights
263
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
264
- copies of the Software, and to permit persons to whom the Software is
265
- furnished to do so, subject to the following conditions:
266
-
267
- The above copyright notice and this permission notice shall be included in
268
- all copies or substantial portions of the Software.
269
-
270
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
271
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
272
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
273
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
274
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
275
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
276
- THE SOFTWARE.