latinum 1.5.0 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -23,46 +23,84 @@
23
23
  require 'bigdecimal'
24
24
  require 'bigdecimal/util'
25
25
 
26
+ require_relative 'error'
27
+
26
28
  module Latinum
27
- class DifferentResourceNameError < ArgumentError
28
- def initialize
29
- super "Cannot operate on different currencies!"
30
- end
31
- end
32
-
33
- # A fixed unit in a given named resource
29
+ # A Resource represents a fixed amount of a named currency or material.
34
30
  class Resource
35
- 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
36
39
 
37
- attr :amount
38
- 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 representatino 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
39
60
 
40
61
  def initialize(amount, name)
41
62
  @amount = amount.to_d
42
63
  @name = name
43
64
  end
44
65
 
45
- # 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.
46
76
  def + other
47
77
  raise DifferentResourceNameError if @name != other.name
48
78
 
49
79
  self.class.new(@amount + other.amount, @name)
50
80
  end
51
81
 
82
+ # Subtract two resources. Must have the same name.
83
+ # @returns [Resource] A resource with the subtracted amount.
52
84
  def - other
53
85
  raise DifferentResourceNameError if @name != other.name
54
86
 
55
87
  self.class.new(@amount - other.amount, @name)
56
88
  end
57
89
 
90
+ # Invert the amount of the resource.
91
+ # @returns [Resource] A resource with the negated amount.
58
92
  def -@
59
93
  self.class.new(-@amount, @name)
60
94
  end
61
95
 
96
+ # Multiplies the resource by a given factor.
97
+ # @returns [Resource] A resource with the updated amount.
62
98
  def * factor
63
99
  self.class.new(@amount * factor, @name)
64
100
  end
65
101
 
102
+ # Divides the resource by a given factor.
103
+ # @returns [Resource] A resource with the updated amount.
66
104
  def / factor
67
105
  if factor.is_a? self.class
68
106
  raise DifferentResourceNameError if @name != factor.name
@@ -73,6 +111,10 @@ module Latinum
73
111
  end
74
112
  end
75
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.
76
118
  def exchange(rate, name, precision = nil)
77
119
  return self if @name == name
78
120
 
@@ -83,20 +125,28 @@ module Latinum
83
125
  self.class.new(exchanged_amount, name)
84
126
  end
85
127
 
128
+ # A human readable string representation of the resource amount and name.
129
+ # @returns [String] e.g. "5 NZD".
86
130
  def to_s
87
131
  "#{@amount.to_s('F')} #{@name}"
88
132
  end
89
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
+
90
140
  def inspect
91
141
  "#<#{self.class.name} #{self.to_s.dump}>"
92
142
  end
93
143
 
94
- # Compare with another resource or a Numeric value.
144
+ # Compare with another {Resource} or a Numeric value.
95
145
  def <=> other
96
146
  if other.is_a? self.class
97
147
  result = @amount <=> other.amount
98
148
  return result unless result == 0
99
-
149
+
100
150
  result = @name <=> other.name
101
151
  return result
102
152
  elsif other.is_a? Numeric
@@ -109,30 +159,11 @@ module Latinum
109
159
  end
110
160
 
111
161
  def eql? other
112
- self.class.eql? other.class and @name.eql? other.name and @amount.eql? other.amount
113
- end
114
-
115
- class << self
116
- def parse(string, default_name: nil)
117
- amount, name = string.split(/\s+/, 2)
118
-
119
- self.new(amount, name || default_name)
120
- end
121
-
122
- def load(string)
123
- if string
124
- # Remove any whitespaces
125
- string = string.strip
126
-
127
- parse(string) unless string.empty?
128
- end
129
- end
130
-
131
- def dump(resource)
132
- resource.to_s if resource
133
- end
162
+ self.class.eql?(other.class) and @name.eql?(other.name) and @amount.eql?(other.amount)
134
163
  end
135
164
 
165
+ # Whether the amount of the resource is zero.
166
+ # @returns [Boolean]
136
167
  def zero?
137
168
  @amount.zero?
138
169
  end
@@ -21,5 +21,5 @@
21
21
  # THE SOFTWARE.
22
22
 
23
23
  module Latinum
24
- VERSION = "1.5.0"
24
+ VERSION = "1.6.0"
25
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.5.0
4
+ version: 1.6.0
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: 2020-02-01 00:00:00.000000000 Z
11
+ date: 2020-07-06 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,7 +93,7 @@ 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
  - - ">="
@@ -113,15 +101,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
113
101
  version: '0'
114
102
  requirements: []
115
103
  rubygems_version: 3.1.2
116
- signing_key:
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
@@ -1,20 +0,0 @@
1
- language: ruby
2
- dist: xenial
3
- cache: bundler
4
-
5
- matrix:
6
- include:
7
- - rvm: 2.4
8
- - rvm: 2.5
9
- - rvm: 2.6
10
- - rvm: 2.6
11
- os: osx
12
- - rvm: 2.6
13
- env: COVERAGE=BriefSummary,Coveralls
14
- - rvm: 2.7
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,5 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- source 'https://rubygems.org'
4
-
5
- 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, name: nil)
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.