danconia 0.2.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/.gitignore +17 -0
- data/.rspec +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +106 -0
- data/Guardfile +5 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +11 -0
- data/danconia.gemspec +26 -0
- data/examples/basic.rb +8 -0
- data/lib/danconia.rb +7 -0
- data/lib/danconia/config.rb +26 -0
- data/lib/danconia/currency.rb +8 -0
- data/lib/danconia/errors/api_error.rb +6 -0
- data/lib/danconia/errors/exchange_rate_not_found.rb +9 -0
- data/lib/danconia/exchange.rb +32 -0
- data/lib/danconia/exchanges/currency_layer.rb +27 -0
- data/lib/danconia/exchanges/fixed_rates.rb +10 -0
- data/lib/danconia/integrations/active_record.rb +36 -0
- data/lib/danconia/kernel.rb +5 -0
- data/lib/danconia/money.rb +95 -0
- data/lib/danconia/stores/in_memory.rb +20 -0
- data/lib/danconia/version.rb +3 -0
- data/spec/danconia/exchanges/currency_layer_spec.rb +56 -0
- data/spec/danconia/exchanges/fixed_rates_spec.rb +30 -0
- data/spec/danconia/integrations/active_record_spec.rb +53 -0
- data/spec/danconia/money_spec.rb +159 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/support/database.rb +12 -0
- data/spec/support/helpers.rb +7 -0
- metadata +179 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: f379e57876333600210d3f3669e1a45654b8016238abd82df38eaf6674084c5c
|
|
4
|
+
data.tar.gz: b85beca8d071d199c13771c14916efc26aac999df2d0dc7bb167597fb5822f29
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 5712b367efd1638949888f2e1fe254a174cd63c23d7e4d14ee8f9601380f4b0cf376b572642fad40d93b4d1d7b8333de2788eff5042fe29e79c36bb9d444315a
|
|
7
|
+
data.tar.gz: b55eab153bed5d5572789b7ed4b7a3cbb6ef8907269a5babfdebe12c1947152baac4c5bde5fc0b4bada904323c6524ceafe0d7991aa64262123362ee22209764
|
data/.gitignore
ADDED
data/.rspec
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
--require spec_helper
|
data/.ruby-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
2.5.0
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
danconia (0.2.1)
|
|
5
|
+
activerecord (>= 3.0.0)
|
|
6
|
+
|
|
7
|
+
GEM
|
|
8
|
+
remote: https://rubygems.org/
|
|
9
|
+
specs:
|
|
10
|
+
activemodel (5.2.0)
|
|
11
|
+
activesupport (= 5.2.0)
|
|
12
|
+
activerecord (5.2.0)
|
|
13
|
+
activemodel (= 5.2.0)
|
|
14
|
+
activesupport (= 5.2.0)
|
|
15
|
+
arel (>= 9.0)
|
|
16
|
+
activesupport (5.2.0)
|
|
17
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
|
18
|
+
i18n (>= 0.7, < 2)
|
|
19
|
+
minitest (~> 5.1)
|
|
20
|
+
tzinfo (~> 1.1)
|
|
21
|
+
addressable (2.5.2)
|
|
22
|
+
public_suffix (>= 2.0.2, < 4.0)
|
|
23
|
+
arel (9.0.0)
|
|
24
|
+
coderay (1.1.2)
|
|
25
|
+
concurrent-ruby (1.0.5)
|
|
26
|
+
crack (0.4.3)
|
|
27
|
+
safe_yaml (~> 1.0.0)
|
|
28
|
+
diff-lcs (1.3)
|
|
29
|
+
ffi (1.9.25)
|
|
30
|
+
formatador (0.2.5)
|
|
31
|
+
guard (2.14.2)
|
|
32
|
+
formatador (>= 0.2.4)
|
|
33
|
+
listen (>= 2.7, < 4.0)
|
|
34
|
+
lumberjack (>= 1.0.12, < 2.0)
|
|
35
|
+
nenv (~> 0.1)
|
|
36
|
+
notiffany (~> 0.0)
|
|
37
|
+
pry (>= 0.9.12)
|
|
38
|
+
shellany (~> 0.0)
|
|
39
|
+
thor (>= 0.18.1)
|
|
40
|
+
guard-compat (1.2.1)
|
|
41
|
+
guard-rspec (4.7.3)
|
|
42
|
+
guard (~> 2.1)
|
|
43
|
+
guard-compat (~> 1.1)
|
|
44
|
+
rspec (>= 2.99.0, < 4.0)
|
|
45
|
+
hashdiff (0.3.7)
|
|
46
|
+
i18n (1.0.1)
|
|
47
|
+
concurrent-ruby (~> 1.0)
|
|
48
|
+
listen (3.1.5)
|
|
49
|
+
rb-fsevent (~> 0.9, >= 0.9.4)
|
|
50
|
+
rb-inotify (~> 0.9, >= 0.9.7)
|
|
51
|
+
ruby_dep (~> 1.2)
|
|
52
|
+
lumberjack (1.0.13)
|
|
53
|
+
method_source (0.9.0)
|
|
54
|
+
minitest (5.11.3)
|
|
55
|
+
nenv (0.3.0)
|
|
56
|
+
notiffany (0.1.1)
|
|
57
|
+
nenv (~> 0.1)
|
|
58
|
+
shellany (~> 0.0)
|
|
59
|
+
pry (0.11.3)
|
|
60
|
+
coderay (~> 1.1.0)
|
|
61
|
+
method_source (~> 0.9.0)
|
|
62
|
+
public_suffix (3.0.2)
|
|
63
|
+
rake (10.3.2)
|
|
64
|
+
rb-fsevent (0.10.3)
|
|
65
|
+
rb-inotify (0.9.10)
|
|
66
|
+
ffi (>= 0.5.0, < 2)
|
|
67
|
+
rspec (3.7.0)
|
|
68
|
+
rspec-core (~> 3.7.0)
|
|
69
|
+
rspec-expectations (~> 3.7.0)
|
|
70
|
+
rspec-mocks (~> 3.7.0)
|
|
71
|
+
rspec-core (3.7.1)
|
|
72
|
+
rspec-support (~> 3.7.0)
|
|
73
|
+
rspec-expectations (3.7.0)
|
|
74
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
75
|
+
rspec-support (~> 3.7.0)
|
|
76
|
+
rspec-mocks (3.7.0)
|
|
77
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
78
|
+
rspec-support (~> 3.7.0)
|
|
79
|
+
rspec-support (3.7.1)
|
|
80
|
+
ruby_dep (1.5.0)
|
|
81
|
+
safe_yaml (1.0.4)
|
|
82
|
+
shellany (0.0.1)
|
|
83
|
+
sqlite3 (1.3.13)
|
|
84
|
+
thor (0.20.0)
|
|
85
|
+
thread_safe (0.3.6)
|
|
86
|
+
tzinfo (1.2.5)
|
|
87
|
+
thread_safe (~> 0.1)
|
|
88
|
+
webmock (3.4.2)
|
|
89
|
+
addressable (>= 2.3.6)
|
|
90
|
+
crack (>= 0.3.2)
|
|
91
|
+
hashdiff
|
|
92
|
+
|
|
93
|
+
PLATFORMS
|
|
94
|
+
ruby
|
|
95
|
+
|
|
96
|
+
DEPENDENCIES
|
|
97
|
+
danconia!
|
|
98
|
+
guard
|
|
99
|
+
guard-rspec
|
|
100
|
+
rake
|
|
101
|
+
rspec
|
|
102
|
+
sqlite3
|
|
103
|
+
webmock
|
|
104
|
+
|
|
105
|
+
BUNDLED WITH
|
|
106
|
+
1.16.1
|
data/Guardfile
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Copyright (c) 2012 Emmanuel Nicolau
|
|
2
|
+
|
|
3
|
+
MIT License
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
a copy of this software and associated documentation files (the
|
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be
|
|
14
|
+
included in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Danconia
|
|
2
|
+
|
|
3
|
+
TODO: Write a gem description
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Add this line to your application's Gemfile:
|
|
8
|
+
|
|
9
|
+
gem 'danconia'
|
|
10
|
+
|
|
11
|
+
And then execute:
|
|
12
|
+
|
|
13
|
+
$ bundle
|
|
14
|
+
|
|
15
|
+
Or install it yourself as:
|
|
16
|
+
|
|
17
|
+
$ gem install danconia
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
TODO: Write usage instructions here
|
|
22
|
+
|
|
23
|
+
## Contributing
|
|
24
|
+
|
|
25
|
+
1. Fork it
|
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
|
27
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/danconia.gemspec
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
require 'danconia/version'
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |gem|
|
|
7
|
+
gem.name = "danconia"
|
|
8
|
+
gem.version = Danconia::VERSION
|
|
9
|
+
gem.authors = ["Emmanuel Nicolau"]
|
|
10
|
+
gem.email = ["emmanicolau@gmail.com"]
|
|
11
|
+
gem.description = %q{Multi-currency money library backed by BigDecimal}
|
|
12
|
+
gem.summary = %q{Multi-currency money library backed by BigDecimal}
|
|
13
|
+
gem.homepage = ""
|
|
14
|
+
|
|
15
|
+
gem.files = `git ls-files`.split($/)
|
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
|
18
|
+
gem.require_paths = ["lib"]
|
|
19
|
+
gem.add_dependency "activerecord", '>= 3.0.0'
|
|
20
|
+
gem.add_development_dependency "sqlite3"
|
|
21
|
+
gem.add_development_dependency "rake"
|
|
22
|
+
gem.add_development_dependency "guard"
|
|
23
|
+
gem.add_development_dependency "guard-rspec"
|
|
24
|
+
gem.add_development_dependency "rspec"
|
|
25
|
+
gem.add_development_dependency "webmock"
|
|
26
|
+
end
|
data/examples/basic.rb
ADDED
data/lib/danconia.rb
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
require 'danconia/exchange'
|
|
2
|
+
|
|
3
|
+
module Danconia
|
|
4
|
+
class << self
|
|
5
|
+
def config
|
|
6
|
+
@config ||= Config.new
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def config= c
|
|
10
|
+
@config = c
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def configure
|
|
14
|
+
yield config
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class Config
|
|
19
|
+
attr_accessor :default_currency, :default_exchange
|
|
20
|
+
|
|
21
|
+
def initialize
|
|
22
|
+
@default_currency = 'USD'
|
|
23
|
+
@default_exchange = Exchanges::FixedRates.new
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
module Danconia
|
|
2
|
+
class Currency < Struct.new(:code, :symbol, :description, keyword_init: true)
|
|
3
|
+
def self.find code, exchange
|
|
4
|
+
return code if code.is_a? Currency
|
|
5
|
+
new exchange.available_currencies.find { |c| c[:code] == code } || {code: code, symbol: '$'}
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
require 'danconia/stores/in_memory'
|
|
2
|
+
|
|
3
|
+
module Danconia
|
|
4
|
+
class Exchange
|
|
5
|
+
attr_reader :store
|
|
6
|
+
|
|
7
|
+
def initialize store: Stores::InMemory.new
|
|
8
|
+
@store = store
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def rate from, to
|
|
12
|
+
if from == 'USD' and direct_rate = @store.direct_rate(from, to)
|
|
13
|
+
direct_rate
|
|
14
|
+
elsif to == 'USD' and inverse_rate = @store.direct_rate(to, from)
|
|
15
|
+
(1.0 / inverse_rate).round 6
|
|
16
|
+
elsif from != 'USD' and to != 'USD' and from_in_usd = rate(from, 'USD') and to_per_usd = rate('USD', to)
|
|
17
|
+
(from_in_usd * to_per_usd).round 6
|
|
18
|
+
else
|
|
19
|
+
raise Errors::ExchangeRateNotFound.new(from, to)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def rates
|
|
24
|
+
@store.rates
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Currently is only needed for money formatting
|
|
28
|
+
def available_currencies
|
|
29
|
+
[]
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require 'danconia/errors/api_error'
|
|
2
|
+
require 'net/http'
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module Danconia
|
|
6
|
+
module Exchanges
|
|
7
|
+
class CurrencyLayer < Exchange
|
|
8
|
+
def initialize access_key:
|
|
9
|
+
super()
|
|
10
|
+
@access_key = access_key
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def update_rates!
|
|
14
|
+
@store.save_rates fetch_rates
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def fetch_rates
|
|
18
|
+
response = JSON.parse Net::HTTP.get URI "http://www.apilayer.net/api/live?access_key=#{@access_key}"
|
|
19
|
+
if response['success']
|
|
20
|
+
response['quotes']
|
|
21
|
+
else
|
|
22
|
+
raise Errors::APIError, response
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
require 'active_record'
|
|
2
|
+
|
|
3
|
+
module Danconia
|
|
4
|
+
module Integrations
|
|
5
|
+
module ActiveRecord
|
|
6
|
+
def self.included(base)
|
|
7
|
+
base.extend(ClassMethods)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
module ClassMethods
|
|
11
|
+
def money(*attr_names)
|
|
12
|
+
attr_names.each do |attr_name|
|
|
13
|
+
amount_column = attr_name
|
|
14
|
+
currency_column = "#{attr_name}_currency"
|
|
15
|
+
|
|
16
|
+
class_eval <<-EOR, __FILE__, __LINE__ + 1
|
|
17
|
+
def #{attr_name}= value
|
|
18
|
+
write_attribute :#{amount_column}, value.is_a?(Money) ? value.amount : value
|
|
19
|
+
write_attribute :#{currency_column}, value.currency.code if respond_to?(:#{currency_column}) && value.is_a?(Money)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def #{attr_name}
|
|
23
|
+
amount = read_attribute :#{amount_column}
|
|
24
|
+
currency = read_attribute :#{currency_column}
|
|
25
|
+
decimals = self.class.columns.detect { |c| c.name == '#{amount_column}' }.scale
|
|
26
|
+
Money.new amount, currency, decimals: decimals if amount
|
|
27
|
+
end
|
|
28
|
+
EOR
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
ActiveRecord::Base.send :include, Danconia::Integrations::ActiveRecord
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
require 'bigdecimal'
|
|
2
|
+
require 'danconia/errors/exchange_rate_not_found'
|
|
3
|
+
|
|
4
|
+
module Danconia
|
|
5
|
+
class Money
|
|
6
|
+
include Comparable
|
|
7
|
+
attr_reader :amount, :currency, :decimals, :exchange
|
|
8
|
+
|
|
9
|
+
def initialize amount, currency_code = nil, decimals: 2, exchange: Danconia.config.default_exchange
|
|
10
|
+
@decimals = decimals
|
|
11
|
+
@amount = parse(amount)
|
|
12
|
+
@currency = Currency.find(currency_code || Danconia.config.default_currency, exchange)
|
|
13
|
+
@exchange = exchange
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def to_s
|
|
17
|
+
ActiveSupport::NumberHelper.number_to_currency amount, precision: decimals, unit: currency.symbol
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def inspect
|
|
21
|
+
"#<#{self.class.name} #{amount} #{currency.code}>"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def == other
|
|
25
|
+
if other.is_a?(Money)
|
|
26
|
+
amount == other.amount && currency == other.currency
|
|
27
|
+
else
|
|
28
|
+
amount == other && currency.code == Danconia.config.default_currency
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def eql? other
|
|
33
|
+
self == other
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def hash
|
|
37
|
+
[amount, currency].hash
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def <=> other
|
|
41
|
+
other = other.exchange_to(currency).amount if other.is_a? Money
|
|
42
|
+
amount <=> other
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def exchange_to other_currency
|
|
46
|
+
other_currency = Currency.find(other_currency, exchange)
|
|
47
|
+
rate = exchange_rate(currency, other_currency)
|
|
48
|
+
clone_with amount * rate, other_currency
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
%w(+ - * /).each do |op|
|
|
52
|
+
class_eval <<-EOR, __FILE__, __LINE__ + 1
|
|
53
|
+
def #{op} other
|
|
54
|
+
other = other.exchange_to(currency).amount if other.is_a? Money
|
|
55
|
+
clone_with amount #{op} other
|
|
56
|
+
end
|
|
57
|
+
EOR
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def round *args
|
|
61
|
+
clone_with amount.round(*args)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def in_cents
|
|
65
|
+
(self * 100).round
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def method_missing method, *args
|
|
69
|
+
if @amount.respond_to? method
|
|
70
|
+
@amount.send method, *args
|
|
71
|
+
else
|
|
72
|
+
super
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def respond_to? method, *args
|
|
77
|
+
super or @amount.respond_to?(method, *args)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
def parse object
|
|
83
|
+
BigDecimal(object.to_s) rescue BigDecimal('0')
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def clone_with amount, currency = @currency
|
|
87
|
+
Money.new amount, currency, decimals: decimals, exchange: exchange
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def exchange_rate from, to
|
|
91
|
+
return 1 if from == to
|
|
92
|
+
exchange.rate from.code, to.code
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Danconia
|
|
2
|
+
module Stores
|
|
3
|
+
class InMemory
|
|
4
|
+
attr_reader :rates
|
|
5
|
+
|
|
6
|
+
def initialize rates: {}
|
|
7
|
+
save_rates rates
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# @rates should be of a map of pair->rate like {'USDEUR' => 1.25}
|
|
11
|
+
def save_rates rates
|
|
12
|
+
@rates = rates
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def direct_rate from, to
|
|
16
|
+
@rates[[from, to].join]
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'danconia/exchanges/currency_layer'
|
|
3
|
+
|
|
4
|
+
module Danconia
|
|
5
|
+
module Exchanges
|
|
6
|
+
describe CurrencyLayer do
|
|
7
|
+
subject { CurrencyLayer.new access_key: '[KEY]' }
|
|
8
|
+
|
|
9
|
+
context 'fetch_rates' do
|
|
10
|
+
it 'uses the API to retrive the rates' do
|
|
11
|
+
stub_request(:get, 'http://www.apilayer.net/api/live?access_key=[KEY]').to_return body: <<~END
|
|
12
|
+
{
|
|
13
|
+
"success": true,
|
|
14
|
+
"source": "USD",
|
|
15
|
+
"quotes": {
|
|
16
|
+
"USDARS": 27.110001,
|
|
17
|
+
"USDAUD": 1.346196
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
END
|
|
21
|
+
expect(subject.fetch_rates).to eq 'USDARS' => 27.110001, 'USDAUD' => 1.346196
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it 'when the API returns an error' do
|
|
25
|
+
stub_request(:get, 'http://www.apilayer.net/api/live?access_key=[KEY]').to_return body: <<~END
|
|
26
|
+
{
|
|
27
|
+
"success": false,
|
|
28
|
+
"error": {
|
|
29
|
+
"code": 104,
|
|
30
|
+
"info": "Your monthly usage limit has been reached. Please upgrade your subscription plan."
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
END
|
|
34
|
+
expect { subject.fetch_rates }.to raise_error Errors::APIError
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
context 'update_rates!' do
|
|
39
|
+
it 'fetches the rates and stores them' do
|
|
40
|
+
expect(subject).to receive(:fetch_rates) { {'USDARS' => 3, 'USDAUD' => 4} }
|
|
41
|
+
subject.update_rates!
|
|
42
|
+
expect(subject.rates.size).to eq 2
|
|
43
|
+
expect(subject.rate('USD', 'ARS')).to eq 3
|
|
44
|
+
expect(subject.rate('USD', 'AUD')).to eq 4
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it 'if a rate already exists should update it' do
|
|
48
|
+
subject.store.save_rates 'USDARS' => 3
|
|
49
|
+
expect(subject).to receive(:fetch_rates) { {'USDARS' => 3.1} }
|
|
50
|
+
subject.update_rates!
|
|
51
|
+
expect(subject.rate('USD', 'ARS')).to eq 3.1
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
module Danconia
|
|
4
|
+
module Exchanges
|
|
5
|
+
describe FixedRates do
|
|
6
|
+
context 'rate' do
|
|
7
|
+
it 'returns the exchange rate value for the supplied currencies' do
|
|
8
|
+
exchange = FixedRates.new rates: {'USDEUR' => 3, 'USDARS' => 4}
|
|
9
|
+
expect(exchange.rate 'USD', 'EUR').to eq 3
|
|
10
|
+
expect(exchange.rate 'USD', 'ARS').to eq 4
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it 'returns nil if not found' do
|
|
14
|
+
expect { subject.rate 'USD', 'EUR' }.to raise_error Errors::ExchangeRateNotFound
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it 'if the direct conversion is not found, tries to find the inverse' do
|
|
18
|
+
exchange = FixedRates.new rates: {'USDEUR' => 3}
|
|
19
|
+
expect(exchange.rate 'EUR', 'USD').to eq (1.0 / 3).round 6
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it 'if not direct nor inverse conversion is found and both are different than USD, tries to convert through USD' do
|
|
23
|
+
exchange = FixedRates.new rates: {'USDEUR' => 3, 'USDARS' => 6}
|
|
24
|
+
expect(exchange.rate 'EUR', 'ARS').to be_within(0.00001).of 2
|
|
25
|
+
expect(exchange.rate 'ARS', 'EUR').to be_within(0.00001).of 0.5
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
module Danconia
|
|
4
|
+
describe Integrations::ActiveRecord, active_record: true do
|
|
5
|
+
context 'single currency' do
|
|
6
|
+
it 'setter' do
|
|
7
|
+
expect(Product.new(price: 1.536).read_attribute :price).to eq 1.54
|
|
8
|
+
expect(Product.new(price: nil).read_attribute :price).to eq nil
|
|
9
|
+
expect(Product.new(price: Money(3)).read_attribute :price).to eq 3
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it 'getter' do
|
|
13
|
+
expect(Product.new(price: 1).price).to eq Money(1)
|
|
14
|
+
expect(Product.new(price: nil).price).to eq nil
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it 'should use the scale from the columns as decimals' do
|
|
18
|
+
expect(Product.new(discount: 2).discount.decimals).to eq 3
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
context 'multicurrency support' do
|
|
23
|
+
it 'setter' do
|
|
24
|
+
expect(Product.new(cost: Money(1, 'ARS')).attributes.values_at('cost', 'cost_currency')).to eq [1, 'ARS']
|
|
25
|
+
expect(Product.new(cost: 2).attributes.values_at('cost', 'cost_currency')).to eq [2, nil]
|
|
26
|
+
expect(Product.new(cost: nil).attributes.values_at('cost', 'cost_currency')).to eq [nil, nil]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it 'getter' do
|
|
30
|
+
expect(Product.new(cost: 1, cost_currency: 'ARS').cost).to eq Money(1, 'ARS')
|
|
31
|
+
expect(Product.new(cost_currency: 'ARS', cost: 1).cost).to eq Money(1, 'ARS')
|
|
32
|
+
expect(Product.new(cost: 1, cost_currency: nil).cost).to eq Money(1, 'USD')
|
|
33
|
+
expect(Product.new(cost: nil, cost_currency: nil).cost).to eq nil
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
class Product < ActiveRecord::Base
|
|
38
|
+
money :price, :tax, :discount, :cost
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
before do
|
|
42
|
+
ActiveRecord::Schema.define version: 1 do
|
|
43
|
+
create_table :products do |t|
|
|
44
|
+
t.column :price, :decimal, precision: 12, scale: 2
|
|
45
|
+
t.column :tax, :decimal, precision: 12, scale: 2
|
|
46
|
+
t.column :discount, :decimal, precision: 12, scale: 3
|
|
47
|
+
t.column :cost, :decimal, precision: 6, scale: 2
|
|
48
|
+
t.column :cost_currency, :string, limit: 3
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
module Danconia
|
|
4
|
+
describe Money do
|
|
5
|
+
context 'instantiation' do
|
|
6
|
+
it 'should accept integers and strings' do
|
|
7
|
+
expect(Money(10).amount).to eq BigDecimal('10')
|
|
8
|
+
expect(Money('10.235').amount).to eq BigDecimal('10.235')
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it 'non numeric values are treated as zero' do
|
|
12
|
+
expect(Money('a')).to eq Money(0)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it 'should use the default currency if not specified' do
|
|
16
|
+
with_config do |config|
|
|
17
|
+
config.default_currency = 'ARS'
|
|
18
|
+
expect(Money(0).currency.code).to eq 'ARS'
|
|
19
|
+
|
|
20
|
+
config.default_currency = 'EUR'
|
|
21
|
+
expect(Money(0).currency.code).to eq 'EUR'
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
context 'arithmetic' do
|
|
27
|
+
it 'does the operation on the amount' do
|
|
28
|
+
expect(Money(3) + Money(2)).to eq Money(5)
|
|
29
|
+
expect(Money(3.5) + 0.5).to eq Money(4)
|
|
30
|
+
expect(Money(78.55) * 0.25).to eq Money(19.6375)
|
|
31
|
+
expect(Money(3) / 2).to eq Money(1.5)
|
|
32
|
+
expect { Money(3.5) + 'a' }.to raise_error TypeError
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it 'should preserve the currency' do
|
|
36
|
+
expect(Money(1, 'ARS') + 2).to eq Money(3, 'ARS')
|
|
37
|
+
expect(Money(1, 'ARS') + Money(2, 'ARS')).to eq Money(3, 'ARS')
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it 'should exchange the other currency if it is different' do
|
|
41
|
+
expect(Money(1, 'ARS') + Money(1, 'USD', exchange: fake_exchange(rate: 4))).to eq Money(5, 'ARS')
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it 'should return a new object with the same options' do
|
|
45
|
+
e = fake_exchange
|
|
46
|
+
m1 = Money(4, decimals: 3, exchange: e)
|
|
47
|
+
m2 = m1 * 2
|
|
48
|
+
expect(m2).to_not eql m1
|
|
49
|
+
expect(m2.decimals).to eq 3
|
|
50
|
+
expect(m2.exchange).to eq e
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it 'round should return a money object with the same currency' do
|
|
54
|
+
expect(Money(1.9, 'ARS').round).to eq Money(2, 'ARS')
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
context 'comparisson' do
|
|
59
|
+
it 'two money objects are equal when the amount and currency are the same' do
|
|
60
|
+
expect(Money(1.01)).to eq Money(1.01)
|
|
61
|
+
expect(Money(1.01)).not_to eq Money(1.02)
|
|
62
|
+
expect(Money(1, 'ARS')).to eq Money(1, 'ARS')
|
|
63
|
+
expect(Money(1, 'USD')).not_to eq Money(1, 'ARS')
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it 'allows to compare against numeric values when using the default currency' do
|
|
67
|
+
expect(Money(1)).to eq 1
|
|
68
|
+
expect(Money(1.35)).to eq 1.35
|
|
69
|
+
expect(Money(1)).not_to eq 2
|
|
70
|
+
expect(Money(1, 'ARS')).not_to eq 1
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it 'should exchange to the source currency if they differ' do
|
|
74
|
+
with_config do |config|
|
|
75
|
+
config.default_exchange = fake_exchange(rate: 4)
|
|
76
|
+
|
|
77
|
+
expect(Money(3, 'ARS') < Money(1, 'USD')).to be true
|
|
78
|
+
expect(Money(4, 'ARS') < Money(1, 'USD')).to be false
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it 'should work with uniq' do
|
|
83
|
+
expect([Money(1), Money(1)].uniq.size).to eq 1
|
|
84
|
+
expect([Money(1), Money(1.1)].uniq.size).to eq 2
|
|
85
|
+
expect([Money(1, 'ARS'), Money(1, 'USD')].uniq.size).to eq 2
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
context 'to_s' do
|
|
90
|
+
it 'should add the currency symbol' do
|
|
91
|
+
expect(Money(3.25).to_s).to eq '$3.25'
|
|
92
|
+
|
|
93
|
+
with_config do |config|
|
|
94
|
+
config.default_exchange = double 'exchange', available_currencies: [{code: 'EUR', symbol: '€'}, {code: 'JPY', symbol: '¥'}]
|
|
95
|
+
|
|
96
|
+
expect(Money(1, 'EUR').to_s).to eq '€1.00'
|
|
97
|
+
expect(Money(1, 'JPY').to_s).to eq '¥1.00'
|
|
98
|
+
expect(Money(1, 'OTHER').to_s).to eq '$1.00'
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
it 'should round according to decimals' do
|
|
103
|
+
expect(Money(3.256, decimals: 2).to_s).to eq '$3.26'
|
|
104
|
+
expect(Money(3.2517, decimals: 3).to_s).to eq '$3.252'
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
it 'nil should be like zero' do
|
|
108
|
+
expect(Money(nil).to_s).to eq '$0.00'
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
context '.inspect' do
|
|
113
|
+
it 'should display the object internals' do
|
|
114
|
+
expect(Money(10.25, 'ARS', decimals: 3).inspect).to eq '#<Danconia::Money 10.25 ARS>'
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
context 'exchange_to' do
|
|
119
|
+
it 'should use the exchange passed to the instance to get the rate' do
|
|
120
|
+
expect(Money(2, 'USD', exchange: fake_exchange(rate: 3)).exchange_to('ARS')).to eq Money(6, 'ARS')
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
it 'should use the default exchange if not set' do
|
|
124
|
+
with_config do |config|
|
|
125
|
+
config.default_exchange = Exchanges::FixedRates.new(rates: {'USDEUR' => 3, 'USDARS' => 4})
|
|
126
|
+
expect(Money(2, 'USD').exchange_to('EUR')).to eq Money(6, 'EUR')
|
|
127
|
+
expect(Money(2, 'USD').exchange_to('ARS')).to eq Money(8, 'ARS')
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
it 'if no rate if found should raise error' do
|
|
132
|
+
expect { Money(2, 'USD').exchange_to('ARS') }.to raise_error Errors::ExchangeRateNotFound
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
it 'exchange between the same currency is always 1' do
|
|
136
|
+
expect(Money(5, 'EUR').exchange_to('EUR')).to eq Money(5, 'EUR')
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
it 'should return a new object with the same opts' do
|
|
140
|
+
m1 = Money(1, 'USD', decimals: 0, exchange: fake_exchange(rate: 3))
|
|
141
|
+
m2 = m1.exchange_to('ARS')
|
|
142
|
+
expect(m2).to_not eql m1
|
|
143
|
+
expect(m2.decimals).to eq 0
|
|
144
|
+
expect(m1).to eq Money(1, 'USD')
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
context 'delegation' do
|
|
149
|
+
it 'should delegate missing methods to the amount' do
|
|
150
|
+
expect(Money(10).positive?).to be true
|
|
151
|
+
expect(Money(10).respond_to?(:positive?)).to be true
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def fake_exchange args = {}
|
|
156
|
+
double 'exchange', args.reverse_merge(rate: nil, available_currencies: [])
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
ActiveRecord::Migration.verbose = false
|
|
2
|
+
ActiveRecord::Base.establish_connection adapter: 'sqlite3', database: ':memory:'
|
|
3
|
+
|
|
4
|
+
module DatabaseCleaner
|
|
5
|
+
def self.included config
|
|
6
|
+
config.after :each, :active_record do
|
|
7
|
+
ActiveRecord::Base.connection.tables.each do |table|
|
|
8
|
+
ActiveRecord::Base.connection.drop_table(table)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: danconia
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.2.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Emmanuel Nicolau
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2018-06-22 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: activerecord
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: 3.0.0
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: 3.0.0
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: sqlite3
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rake
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '0'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: guard
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '0'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: guard-rspec
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - ">="
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '0'
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - ">="
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '0'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: rspec
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - ">="
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '0'
|
|
90
|
+
type: :development
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - ">="
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '0'
|
|
97
|
+
- !ruby/object:Gem::Dependency
|
|
98
|
+
name: webmock
|
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - ">="
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '0'
|
|
104
|
+
type: :development
|
|
105
|
+
prerelease: false
|
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - ">="
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '0'
|
|
111
|
+
description: Multi-currency money library backed by BigDecimal
|
|
112
|
+
email:
|
|
113
|
+
- emmanicolau@gmail.com
|
|
114
|
+
executables: []
|
|
115
|
+
extensions: []
|
|
116
|
+
extra_rdoc_files: []
|
|
117
|
+
files:
|
|
118
|
+
- ".gitignore"
|
|
119
|
+
- ".rspec"
|
|
120
|
+
- ".ruby-version"
|
|
121
|
+
- Gemfile
|
|
122
|
+
- Gemfile.lock
|
|
123
|
+
- Guardfile
|
|
124
|
+
- LICENSE.txt
|
|
125
|
+
- README.md
|
|
126
|
+
- Rakefile
|
|
127
|
+
- danconia.gemspec
|
|
128
|
+
- examples/basic.rb
|
|
129
|
+
- lib/danconia.rb
|
|
130
|
+
- lib/danconia/config.rb
|
|
131
|
+
- lib/danconia/currency.rb
|
|
132
|
+
- lib/danconia/errors/api_error.rb
|
|
133
|
+
- lib/danconia/errors/exchange_rate_not_found.rb
|
|
134
|
+
- lib/danconia/exchange.rb
|
|
135
|
+
- lib/danconia/exchanges/currency_layer.rb
|
|
136
|
+
- lib/danconia/exchanges/fixed_rates.rb
|
|
137
|
+
- lib/danconia/integrations/active_record.rb
|
|
138
|
+
- lib/danconia/kernel.rb
|
|
139
|
+
- lib/danconia/money.rb
|
|
140
|
+
- lib/danconia/stores/in_memory.rb
|
|
141
|
+
- lib/danconia/version.rb
|
|
142
|
+
- spec/danconia/exchanges/currency_layer_spec.rb
|
|
143
|
+
- spec/danconia/exchanges/fixed_rates_spec.rb
|
|
144
|
+
- spec/danconia/integrations/active_record_spec.rb
|
|
145
|
+
- spec/danconia/money_spec.rb
|
|
146
|
+
- spec/spec_helper.rb
|
|
147
|
+
- spec/support/database.rb
|
|
148
|
+
- spec/support/helpers.rb
|
|
149
|
+
homepage: ''
|
|
150
|
+
licenses: []
|
|
151
|
+
metadata: {}
|
|
152
|
+
post_install_message:
|
|
153
|
+
rdoc_options: []
|
|
154
|
+
require_paths:
|
|
155
|
+
- lib
|
|
156
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
157
|
+
requirements:
|
|
158
|
+
- - ">="
|
|
159
|
+
- !ruby/object:Gem::Version
|
|
160
|
+
version: '0'
|
|
161
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
162
|
+
requirements:
|
|
163
|
+
- - ">="
|
|
164
|
+
- !ruby/object:Gem::Version
|
|
165
|
+
version: '0'
|
|
166
|
+
requirements: []
|
|
167
|
+
rubyforge_project:
|
|
168
|
+
rubygems_version: 2.7.3
|
|
169
|
+
signing_key:
|
|
170
|
+
specification_version: 4
|
|
171
|
+
summary: Multi-currency money library backed by BigDecimal
|
|
172
|
+
test_files:
|
|
173
|
+
- spec/danconia/exchanges/currency_layer_spec.rb
|
|
174
|
+
- spec/danconia/exchanges/fixed_rates_spec.rb
|
|
175
|
+
- spec/danconia/integrations/active_record_spec.rb
|
|
176
|
+
- spec/danconia/money_spec.rb
|
|
177
|
+
- spec/spec_helper.rb
|
|
178
|
+
- spec/support/database.rb
|
|
179
|
+
- spec/support/helpers.rb
|