experimental-money 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gems +1 -0
- data/Challenge.md +71 -0
- data/README.md +100 -0
- data/experimental-money.gemspec +14 -0
- data/lib/experimental-money.rb +88 -0
- data/makefile +4 -0
- data/test/money.rb +67 -0
- metadata +65 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 635267ff25b3ccfccd66e5f8394f63d398b9fe5d
|
4
|
+
data.tar.gz: 433e055f791c12cf60eb71d9be04cf51d75ced6c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4ea02386d29e47978d7ccda94291dd5ba71a11916bf8ab161ef99bfbe212e950bce85dea295a3e53ef59341ff6d9c11a11f94a590c15c6d8e877d40072ca33cd
|
7
|
+
data.tar.gz: 326812d7421639b0eeb7f1f3e753fa73f99a2cd9f6679de31761366fb0387176b625812e8529ac8e6796d4e9efc191bf540a1af4dc54d822abb26ef91bbef0ff
|
data/.gems
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
cutest -v 1.2.3
|
data/Challenge.md
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
# Money test
|
2
|
+
|
3
|
+
Crear una gema que pueda manejar las diferentes conversiones de monedas, hacer operaciones aritméticas y demas.
|
4
|
+
Las caractersticas y el uso serán descritos a continuacion
|
5
|
+
|
6
|
+
Configurar la moneda default y sus respectivas conversiones (here, EUR)
|
7
|
+
```
|
8
|
+
Money.configure do |config|
|
9
|
+
config.default_currency = "EUR"
|
10
|
+
config.conversions = {
|
11
|
+
'USD' => 1.11,
|
12
|
+
'Bitcoin' => 0.0047
|
13
|
+
}
|
14
|
+
end
|
15
|
+
```
|
16
|
+
|
17
|
+
#### Instanciar objeto
|
18
|
+
```
|
19
|
+
fifty_eur = Money.new(50, 'EUR')
|
20
|
+
```
|
21
|
+
|
22
|
+
#### Get monto y moneda
|
23
|
+
```
|
24
|
+
fifty_eur.amount # => 50
|
25
|
+
fifty_eur.currency # => "EUR"
|
26
|
+
fifty_eur.inspect # => "50.00 EUR"
|
27
|
+
```
|
28
|
+
|
29
|
+
#### Convertir a otra moneda (Debe devolver una instancia, no un string)
|
30
|
+
```
|
31
|
+
fifty_eur.convert_to('USD') # => 55.50 USD
|
32
|
+
```
|
33
|
+
|
34
|
+
#### Realizar operaciones aritmeticas
|
35
|
+
```
|
36
|
+
twenty_dollars = Money.new(20, 'USD')
|
37
|
+
```
|
38
|
+
|
39
|
+
### Aritmeticas:
|
40
|
+
```
|
41
|
+
fifty_eur + twenty_dollars # => 68.02 EUR
|
42
|
+
fifty_eur - twenty_dollars # => 31.98 EUR
|
43
|
+
fifty_eur / 2 # => 25 EUR
|
44
|
+
twenty_dollars * 3 # => 60 USD
|
45
|
+
```
|
46
|
+
|
47
|
+
#### Comparaciones (también en diferentes monedas):
|
48
|
+
```
|
49
|
+
twenty_dollars == Money.new(20, 'USD') # => true
|
50
|
+
twenty_dollars == Money.new(30, 'USD') # => false
|
51
|
+
|
52
|
+
fifty_eur_in_usd = fifty_eur.convert_to('USD')
|
53
|
+
fifty_eur_in_usd == fifty_eur # => true
|
54
|
+
|
55
|
+
twenty_dollars > Money.new(5, 'USD') # => true
|
56
|
+
twenty_dollars < fifty_eur # => true
|
57
|
+
```
|
58
|
+
|
59
|
+
La idea del test es que cuente con sus respectivos tests. Además se analizará calidad del codigo.
|
60
|
+
Objeto Money no debe conetener más caracteristicas que las mencionadas, please keep it simple.
|
61
|
+
Tambien la gema debe estar subida a RubyGems y debe ser instalable desde
|
62
|
+
```
|
63
|
+
gem install your_gem
|
64
|
+
```
|
65
|
+
|
66
|
+
y debe ser capaz de ser cargada desde irb
|
67
|
+
```
|
68
|
+
irb
|
69
|
+
require 'your_gem'
|
70
|
+
Money.new
|
71
|
+
```
|
data/README.md
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
Experimental Money
|
2
|
+
====
|
3
|
+
|
4
|
+
|
5
|
+
Installation
|
6
|
+
------------
|
7
|
+
|
8
|
+
``` console
|
9
|
+
$ gem install experimental-money
|
10
|
+
```
|
11
|
+
|
12
|
+
Usage
|
13
|
+
-----
|
14
|
+
|
15
|
+
Here's a simple example:
|
16
|
+
|
17
|
+
``` ruby
|
18
|
+
Money.configure do |config|
|
19
|
+
config.default_currency = "EUR"
|
20
|
+
config.conversions = {
|
21
|
+
'USD' => 1.11,
|
22
|
+
'Bitcoin' => 0.0047
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
fifty_eur = Money.new(50, 'EUR')
|
27
|
+
fifty_eur.amount # => 50
|
28
|
+
fifty_eur.currency # => "EUR"
|
29
|
+
fifty_eur.inspect # => "50.00 EUR"
|
30
|
+
|
31
|
+
# Currency exchange
|
32
|
+
fifty_eur.convert_to('USD') # => 55.50 USD
|
33
|
+
|
34
|
+
# Arithmetic
|
35
|
+
|
36
|
+
twenty_dollars = Money.new(20, 'USD')
|
37
|
+
fifty_eur + twenty_dollars # => 68.02 EUR
|
38
|
+
fifty_eur - twenty_dollars # => 31.98 EUR
|
39
|
+
fifty_eur / 2 # => 25 EUR
|
40
|
+
twenty_dollars * 3 # => 60 USD
|
41
|
+
|
42
|
+
# Comparations
|
43
|
+
|
44
|
+
twenty_dollars == Money.new(20, 'USD') # => true
|
45
|
+
twenty_dollars == Money.new(30, 'USD') # => false
|
46
|
+
|
47
|
+
fifty_eur_in_usd = fifty_eur.convert_to('USD')
|
48
|
+
fifty_eur_in_usd == fifty_eur # => true
|
49
|
+
|
50
|
+
twenty_dollars > Money.new(5, 'USD') # => true
|
51
|
+
twenty_dollars < fifty_eur # => true
|
52
|
+
|
53
|
+
```
|
54
|
+
|
55
|
+
Caveats
|
56
|
+
------------
|
57
|
+
|
58
|
+
- We are using BigDecimals. Another option could be using Integers
|
59
|
+
instead. As our desired API expects to initialize Money as `Money.new(float_amount, currency)` we already needed to convert the amount to BigDecimal in order to avoid `Float` precision errors. Integers should work way faster, though.
|
60
|
+
- Precision and rounding: When talking about money we need to know its
|
61
|
+
currency precision. The normal case is having two decimal positions
|
62
|
+
in order to represent `cents`. Other currencies use up to 8 decimal
|
63
|
+
places (to represent `Satoshis`).
|
64
|
+
|
65
|
+
I arbitrarily set up Bitcoin precision (8) and set a default of (2)
|
66
|
+
for the remaining currencies. A pull request would be needed to add
|
67
|
+
weird currencies with different precision.
|
68
|
+
|
69
|
+
Rounding:
|
70
|
+
When dividing money (or converting it to another currency -which
|
71
|
+
involves a division) we end up often with a number that has to be
|
72
|
+
rounded. There are many ways to round 0.006 to a two decimal places
|
73
|
+
number representing USD. I hardcoded the Bankers' rounding way, but
|
74
|
+
for some problems it may be suitable another method.
|
75
|
+
|
76
|
+
- Last caveat: Arithmetic doesn't always represent Money operations as
|
77
|
+
we expect. If we need to divide 100USD among three people and we try
|
78
|
+
to do `Money.new(100, "USD")/3` to know how much each of them is
|
79
|
+
receiving we'll find a surprising result: It's not possible to
|
80
|
+
equally divide 100USD. With the current implementation this operation
|
81
|
+
would return `Money.new(33.33, "USD")`
|
82
|
+
|
83
|
+
|
84
|
+
Contributing
|
85
|
+
------------
|
86
|
+
|
87
|
+
If you want to test this gem, you may want to use a gemset to isolate
|
88
|
+
the requirements. We recommend the use of tools like [dep][dep] and
|
89
|
+
[gs][gs], but you can use similar tools like [gst][gst] or [bs][bs].
|
90
|
+
|
91
|
+
The required gems for testing and development are listed in the
|
92
|
+
`.gems` file. If you are using [dep][dep], you can create a gemset
|
93
|
+
and run `dep install`.
|
94
|
+
|
95
|
+
After `cutest` is installed you can run the tests by doing `make`
|
96
|
+
|
97
|
+
[dep]: http://cyx.github.io/dep/
|
98
|
+
[gs]: http://soveran.github.io/gs/
|
99
|
+
[gst]: https://github.com/tonchis/gst
|
100
|
+
[bs]: https://github.com/educabilia/bs
|
@@ -0,0 +1,14 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "experimental-money"
|
3
|
+
s.version = "0.0.1"
|
4
|
+
s.summary = "Money arithmetic with BigDecimal"
|
5
|
+
s.description = "Money arithmetic with BigDecimal"
|
6
|
+
s.authors = ["CarlosIPe"]
|
7
|
+
s.email = ["carlos2@compendium.com.ar"]
|
8
|
+
s.homepage = "https://github.com/carlosipe/experimental-money"
|
9
|
+
s.license = "MIT"
|
10
|
+
|
11
|
+
s.files = `git ls-files`.split("\n")
|
12
|
+
|
13
|
+
s.add_development_dependency "cutest", '~> 1'
|
14
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'bigdecimal'
|
2
|
+
|
3
|
+
class Money
|
4
|
+
class << self
|
5
|
+
attr_writer :configuration
|
6
|
+
|
7
|
+
def configuration
|
8
|
+
@configuration ||= Configuration.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def configure
|
12
|
+
yield(configuration)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Configuration
|
17
|
+
attr_writer :default_currency
|
18
|
+
def conversions=(hash)
|
19
|
+
@conversions = hash.map { |k, v| [k, BigDecimal(v.to_s)] }.to_h
|
20
|
+
end
|
21
|
+
|
22
|
+
def conversions
|
23
|
+
raise('No conversions configured') unless @conversions
|
24
|
+
@conversions.merge(default_currency => 1)
|
25
|
+
end
|
26
|
+
|
27
|
+
def default_currency
|
28
|
+
raise('Default currency is not configured') unless @default_currency
|
29
|
+
@default_currency
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
include Comparable
|
34
|
+
|
35
|
+
attr_reader :currency, :amount
|
36
|
+
def initialize(amount, currency)
|
37
|
+
raise 'Invalid currency' unless conversions.member?(currency)
|
38
|
+
@currency = currency
|
39
|
+
@amount = BigDecimal(amount.to_s).round(currency_precision)
|
40
|
+
end
|
41
|
+
|
42
|
+
def currency_precision
|
43
|
+
{
|
44
|
+
'Bitcoin' => 8,
|
45
|
+
}.fetch(currency, 2)
|
46
|
+
end
|
47
|
+
|
48
|
+
def convert_to(new_currency)
|
49
|
+
new_amount = amount * conversions.fetch(new_currency) / conversions.fetch(currency)
|
50
|
+
self.class.new(new_amount, new_currency)
|
51
|
+
end
|
52
|
+
|
53
|
+
def <=>(other)
|
54
|
+
amount <=> other.convert_to(currency).amount
|
55
|
+
end
|
56
|
+
|
57
|
+
def inspect
|
58
|
+
"#{format('%.2f', amount)} #{currency}"
|
59
|
+
end
|
60
|
+
|
61
|
+
def conversions
|
62
|
+
self.class.configuration.conversions
|
63
|
+
end
|
64
|
+
|
65
|
+
def +(other)
|
66
|
+
return Money.new(amount + other.amount, currency) if currency == other.currency
|
67
|
+
total = convert_to(default_currency).amount + other.convert_to(default_currency).amount
|
68
|
+
self.class.new(total, default_currency)
|
69
|
+
end
|
70
|
+
|
71
|
+
def -(other)
|
72
|
+
return Money.new(amount - other.amount, currency) if currency == other.currency
|
73
|
+
total = convert_to(default_currency).amount - other.convert_to(default_currency).amount
|
74
|
+
self.class.new(total, default_currency)
|
75
|
+
end
|
76
|
+
|
77
|
+
def /(num)
|
78
|
+
Money.new(amount / num, currency)
|
79
|
+
end
|
80
|
+
|
81
|
+
def *(num)
|
82
|
+
Money.new(amount * num, currency)
|
83
|
+
end
|
84
|
+
|
85
|
+
def default_currency
|
86
|
+
self.class.configuration.default_currency
|
87
|
+
end
|
88
|
+
end
|
data/makefile
ADDED
data/test/money.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.expand_path('../lib', File.dirname(__FILE__)))
|
2
|
+
require 'money'
|
3
|
+
|
4
|
+
Money.configure do |config|
|
5
|
+
config.default_currency = 'EUR'
|
6
|
+
config.conversions = {
|
7
|
+
'USD' => 1.11,
|
8
|
+
'Bitcoin' => 0.0047
|
9
|
+
}
|
10
|
+
end
|
11
|
+
|
12
|
+
def fifty_eur
|
13
|
+
Money.new(50, 'EUR')
|
14
|
+
end
|
15
|
+
|
16
|
+
test '.amount' do
|
17
|
+
assert_equal(fifty_eur.amount, 50)
|
18
|
+
end
|
19
|
+
|
20
|
+
test '.currency' do
|
21
|
+
assert_equal(fifty_eur.currency, 'EUR')
|
22
|
+
end
|
23
|
+
|
24
|
+
test '.inspect' do
|
25
|
+
assert_equal(fifty_eur.inspect, '50.00 EUR')
|
26
|
+
end
|
27
|
+
|
28
|
+
test '.convert_to' do
|
29
|
+
assert_equal(fifty_eur.convert_to('USD'), Money.new(55.50, 'USD'))
|
30
|
+
end
|
31
|
+
|
32
|
+
test 'sum different currencies' do
|
33
|
+
assert_equal(Money.new(50, 'EUR') + Money.new(20, 'USD'), Money.new(68.02, 'EUR'))
|
34
|
+
end
|
35
|
+
|
36
|
+
test 'subtract different currencies' do
|
37
|
+
assert_equal(Money.new(50, 'EUR') - Money.new(20, 'USD'), Money.new(31.98, 'EUR'))
|
38
|
+
end
|
39
|
+
|
40
|
+
test 'dividing money by scalar' do
|
41
|
+
assert_equal(Money.new(50, 'EUR') / 2, Money.new(25, 'EUR'))
|
42
|
+
end
|
43
|
+
|
44
|
+
test 'multiplying money by scalar' do
|
45
|
+
assert_equal(Money.new(20, 'USD') * 3, Money.new(60, 'USD'))
|
46
|
+
end
|
47
|
+
|
48
|
+
test 'Equal compare on same amount and currency' do
|
49
|
+
assert_equal(Money.new(20, 'USD'), Money.new(20, 'USD'))
|
50
|
+
end
|
51
|
+
|
52
|
+
test 'not equal on same currency and different amount' do
|
53
|
+
assert Money.new(20, 'USD') != Money.new(30, 'USD')
|
54
|
+
end
|
55
|
+
|
56
|
+
test 'equal on different currencies but equivalent amount' do
|
57
|
+
fifty_eur_in_usd = fifty_eur.convert_to('USD')
|
58
|
+
assert_equal(fifty_eur, fifty_eur_in_usd)
|
59
|
+
end
|
60
|
+
|
61
|
+
test 'greater than' do
|
62
|
+
assert Money.new(20, 'USD') > Money.new(5, 'USD')
|
63
|
+
end
|
64
|
+
|
65
|
+
test 'less than' do
|
66
|
+
assert Money.new(20, 'USD') < Money.new(50, 'EUR')
|
67
|
+
end
|
metadata
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: experimental-money
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- CarlosIPe
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-01-25 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: cutest
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1'
|
27
|
+
description: Money arithmetic with BigDecimal
|
28
|
+
email:
|
29
|
+
- carlos2@compendium.com.ar
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- ".gems"
|
35
|
+
- Challenge.md
|
36
|
+
- README.md
|
37
|
+
- experimental-money.gemspec
|
38
|
+
- lib/experimental-money.rb
|
39
|
+
- makefile
|
40
|
+
- test/money.rb
|
41
|
+
homepage: https://github.com/carlosipe/experimental-money
|
42
|
+
licenses:
|
43
|
+
- MIT
|
44
|
+
metadata: {}
|
45
|
+
post_install_message:
|
46
|
+
rdoc_options: []
|
47
|
+
require_paths:
|
48
|
+
- lib
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: '0'
|
59
|
+
requirements: []
|
60
|
+
rubyforge_project:
|
61
|
+
rubygems_version: 2.5.1
|
62
|
+
signing_key:
|
63
|
+
specification_version: 4
|
64
|
+
summary: Money arithmetic with BigDecimal
|
65
|
+
test_files: []
|