experimental-money 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|