danconia 0.2.8 → 0.2.9

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fc840f5913a010c41495c7fd843304d67565217acf7eb7c248be9bf4c17624f8
4
- data.tar.gz: d8906df663f918a41d91525b6ac51de9f8ac6decef179c813fc30145be876c50
3
+ metadata.gz: a7427977cd93825975491eddf43bcd9e5fda9389102d2c2b32c16efdfb11f590
4
+ data.tar.gz: 15bccf8fc66c26679cc101d5f27dd9aabc5398b28fe1a04a00b167500993eedf
5
5
  SHA512:
6
- metadata.gz: dc66bbeaf202e6b288f79d4a6fb9b93cc66585771f9543c14a14980252f5a112eb4e6a2cad21d15e4132bda2400f74cdb5567eb9a47a4f7ac7b6161105c2ae9c
7
- data.tar.gz: 222d602fb10b842955d921ede2c7bae8bba4fd56dcdb1bcbcc74c12f795959c58250141f05943b607f0de80cdb3a07dc7425219e38f534c375236a0029d29ce2
6
+ metadata.gz: 95d1edfd74b3388998c3251ac204a61a10a7454e0eda2fc2ca3b6105f95d9ead47c511a67790e82d34d4d54f934db83cc7326da53abef51c074846571b28a819
7
+ data.tar.gz: 033bb6ae1f171059bceb87495b9490b2d760f6371d71ec74f8a6a494f628ab52302f91f19f2a0e9f344c9c4775fecd0027c071e0cc21db51568e1efd80ea8c70
@@ -1,18 +1,18 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- danconia (0.2.8)
4
+ danconia (0.2.9)
5
5
  activerecord (>= 3.0.0)
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
- activemodel (6.0.3.1)
11
- activesupport (= 6.0.3.1)
12
- activerecord (6.0.3.1)
13
- activemodel (= 6.0.3.1)
14
- activesupport (= 6.0.3.1)
15
- activesupport (6.0.3.1)
10
+ activemodel (6.0.3.2)
11
+ activesupport (= 6.0.3.2)
12
+ activerecord (6.0.3.2)
13
+ activemodel (= 6.0.3.2)
14
+ activesupport (= 6.0.3.2)
15
+ activesupport (6.0.3.2)
16
16
  concurrent-ruby (~> 1.0, >= 1.0.2)
17
17
  i18n (>= 0.7, < 2)
18
18
  minitest (~> 5.1)
@@ -21,7 +21,7 @@ GEM
21
21
  addressable (2.5.2)
22
22
  public_suffix (>= 2.0.2, < 4.0)
23
23
  coderay (1.1.2)
24
- concurrent-ruby (1.1.6)
24
+ concurrent-ruby (1.1.7)
25
25
  crack (0.4.3)
26
26
  safe_yaml (~> 1.0.0)
27
27
  diff-lcs (1.3)
@@ -42,7 +42,7 @@ GEM
42
42
  guard-compat (~> 1.1)
43
43
  rspec (>= 2.99.0, < 4.0)
44
44
  hashdiff (0.3.7)
45
- i18n (1.8.3)
45
+ i18n (1.8.5)
46
46
  concurrent-ruby (~> 1.0)
47
47
  listen (3.1.5)
48
48
  rb-fsevent (~> 0.9, >= 0.9.4)
@@ -50,7 +50,7 @@ GEM
50
50
  ruby_dep (~> 1.2)
51
51
  lumberjack (1.0.13)
52
52
  method_source (0.9.0)
53
- minitest (5.14.1)
53
+ minitest (5.14.2)
54
54
  nenv (0.3.0)
55
55
  notiffany (0.1.1)
56
56
  nenv (~> 0.1)
@@ -88,7 +88,7 @@ GEM
88
88
  addressable (>= 2.3.6)
89
89
  crack (>= 0.3.2)
90
90
  hashdiff
91
- zeitwerk (2.3.0)
91
+ zeitwerk (2.4.0)
92
92
 
93
93
  PLATFORMS
94
94
  ruby
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'danconia'
5
+
6
+ require 'pry'
7
+ Pry.start
@@ -1,6 +1,5 @@
1
1
  # Remember to supply your CurrencyLayer key in the ACCESS_KEY environment variable to run this example
2
- require 'danconia'
3
-
2
+ ActiveRecord::Base.logger = Logger.new(STDOUT)
4
3
  ActiveRecord::Base.establish_connection adapter: 'sqlite3', database: ':memory:'
5
4
 
6
5
  ActiveRecord::Schema.define do
@@ -1,8 +1,6 @@
1
- require 'danconia'
2
-
3
1
  Danconia.configure do |config|
4
2
  config.default_currency = 'ARS'
5
3
  config.default_exchange = Danconia::Exchanges::FixedRates.new(rates: {'USDARS' => 27.5, 'USDEUR' => 0.86})
6
4
  end
7
5
 
8
- puts Money(10, 'ARS').exchange_to('EUR').inspect # => 0.31273 EUR
6
+ puts Money(10, 'ARS').exchange_to('EUR').inspect # => 0.31273 EUR
@@ -1,5 +1,3 @@
1
- require 'danconia'
2
-
3
1
  # USD is the default currency if no configuration is provided
4
2
  puts (Money(10.25) / 2).inspect # => 5.125 USD
5
3
  puts (Money(10.25) / 2).to_s # => $5.13
@@ -1,3 +1,5 @@
1
+ require 'danconia/pair'
2
+
1
3
  module Danconia
2
4
  module Exchanges
3
5
  class Exchange
@@ -8,17 +10,11 @@ module Danconia
8
10
  end
9
11
 
10
12
  def rate from, to
11
- if from == to
12
- 1.0
13
- elsif from == 'USD' and direct_rate = @store.direct_rate(from, to)
14
- direct_rate
15
- elsif to == 'USD' and inverse_rate = @store.direct_rate(to, from)
16
- (1.0 / inverse_rate).round 6
17
- elsif from != 'USD' and to != 'USD' and from_in_usd = rate(from, 'USD') and to_per_usd = rate('USD', to)
18
- (from_in_usd * to_per_usd).round 6
19
- else
20
- raise Errors::ExchangeRateNotFound.new(from, to)
21
- end
13
+ return 1.0 if from == to
14
+
15
+ pair = Pair.new(from, to)
16
+ rates = direct_and_inverted_rates()
17
+ rates[pair] or indirect_rate(pair, rates) or raise Errors::ExchangeRateNotFound.new(from, to)
22
18
  end
23
19
 
24
20
  def rates
@@ -28,6 +24,24 @@ module Danconia
28
24
  def update_rates!
29
25
  @store.save_rates fetch_rates
30
26
  end
27
+
28
+ private
29
+
30
+ # Returns the original rates plus the inverted ones, to simplify rate finding logic.
31
+ def direct_and_inverted_rates
32
+ rates.each_with_object({}) do |(pair_str, rate), rs|
33
+ pair = Pair.parse(pair_str)
34
+ rs[pair] = rate
35
+ rs[pair.invert] ||= 1.0 / rate
36
+ end
37
+ end
38
+
39
+ def indirect_rate ind_pair, rates
40
+ if (from_pair = rates.keys.detect { |(pair, rate)| pair.from == ind_pair.from }) &&
41
+ (to_pair = rates.keys.detect { |(pair, rate)| pair.to == ind_pair.to })
42
+ rates[from_pair] * rates[to_pair]
43
+ end
44
+ end
31
45
  end
32
46
  end
33
47
  end
@@ -0,0 +1,15 @@
1
+ module Danconia
2
+ Pair = Struct.new(:from, :to) do
3
+ def self.parse str
4
+ new str[0..2], str[3..-1]
5
+ end
6
+
7
+ def invert
8
+ Pair.new to, from
9
+ end
10
+
11
+ def to_s
12
+ [from, to].join
13
+ end
14
+ end
15
+ end
@@ -9,10 +9,6 @@ module Danconia
9
9
  end
10
10
  end
11
11
 
12
- def direct_rate from, to
13
- ExchangeRate.find_by(pair: [from, to].join)&.rate
14
- end
15
-
16
12
  def rates
17
13
  Hash[ExchangeRate.all.map { |er| [er.pair, er.rate] }]
18
14
  end
@@ -3,18 +3,14 @@ module Danconia
3
3
  class InMemory
4
4
  attr_reader :rates
5
5
 
6
+ # `rates` should be of a map of pair->rate like {'USDEUR' => 1.25}
6
7
  def initialize rates: {}
7
8
  save_rates rates
8
9
  end
9
10
 
10
- # @rates should be of a map of pair->rate like {'USDEUR' => 1.25}
11
11
  def save_rates rates
12
12
  @rates = rates
13
13
  end
14
-
15
- def direct_rate from, to
16
- @rates[[from, to].join]
17
- end
18
14
  end
19
15
  end
20
16
  end
@@ -1,3 +1,3 @@
1
1
  module Danconia
2
- VERSION = '0.2.8'
2
+ VERSION = '0.2.9'
3
3
  end
@@ -7,29 +7,12 @@ module Danconia
7
7
 
8
8
  context 'fetch_rates' do
9
9
  it 'uses the API to retrive the rates' do
10
- stub_request(:get, 'http://www.apilayer.net/api/live?access_key=[KEY]').to_return body: <<~END
11
- {
12
- "success": true,
13
- "source": "USD",
14
- "quotes": {
15
- "USDARS": 27.110001,
16
- "USDAUD": 1.346196
17
- }
18
- }
19
- END
10
+ stub_request(:get, 'http://www.apilayer.net/api/live?access_key=[KEY]').to_return body: fixture('success.json')
20
11
  expect(subject.fetch_rates).to eq 'USDARS' => 27.110001, 'USDAUD' => 1.346196
21
12
  end
22
13
 
23
14
  it 'when the API returns an error' do
24
- stub_request(:get, 'http://www.apilayer.net/api/live?access_key=[KEY]').to_return body: <<~END
25
- {
26
- "success": false,
27
- "error": {
28
- "code": 104,
29
- "info": "Your monthly usage limit has been reached. Please upgrade your subscription plan."
30
- }
31
- }
32
- END
15
+ stub_request(:get, 'http://www.apilayer.net/api/live?access_key=[KEY]').to_return body: fixture('failure.json')
33
16
  expect { subject.fetch_rates }.to raise_error Errors::APIError
34
17
  end
35
18
  end
@@ -38,7 +21,7 @@ module Danconia
38
21
  it 'fetches the rates and stores them' do
39
22
  expect(subject).to receive(:fetch_rates) { {'USDARS' => 3, 'USDAUD' => 4} }
40
23
  subject.update_rates!
41
- expect(subject.rates.size).to eq 2
24
+ expect(subject.store.rates.size).to eq 2
42
25
  expect(subject.rate('USD', 'ARS')).to eq 3
43
26
  expect(subject.rate('USD', 'AUD')).to eq 4
44
27
  end
@@ -50,6 +33,10 @@ module Danconia
50
33
  expect(subject.rate('USD', 'ARS')).to eq 3.1
51
34
  end
52
35
  end
36
+
37
+ def fixture file
38
+ File.read("#{__dir__}/fixtures/currency_layer/#{file}")
39
+ end
53
40
  end
54
41
  end
55
- end
42
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+
3
+ module Danconia
4
+ module Exchanges
5
+ describe Exchange do
6
+ context 'rate' do
7
+ it 'returns the exchange rate value for the supplied currencies' do
8
+ exchange = fake_exchange('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 'if the direct conversion is not found, tries to find the inverse' do
14
+ exchange = fake_exchange('USDEUR' => 3)
15
+ expect(exchange.rate('EUR', 'USD')).to be_within(0.00001).of(1.0 / 3)
16
+ end
17
+
18
+ it 'if not direct nor inverse conversion is found, tries to convert through USD' do
19
+ exchange = fake_exchange('USDEUR' => 3, 'USDARS' => 6)
20
+ expect(exchange.rate('EUR', 'ARS')).to be_within(0.00001).of 2
21
+ expect(exchange.rate('ARS', 'EUR')).to be_within(0.00001).of 0.5
22
+ end
23
+
24
+ it 'pairs can have a different common currency' do
25
+ exchange = fake_exchange('EURARS' => 3, 'BRLARS' => 1.5)
26
+ expect(exchange.rate('EUR', 'ARS')).to eq 3
27
+ expect(exchange.rate('ARS', 'EUR')).to be_within(0.00001).of(1.0 / 3)
28
+ expect(exchange.rate('BRL', 'ARS')).to eq 1.5
29
+ expect(exchange.rate('EUR', 'BRL')).to eq 3 / 1.5
30
+ end
31
+
32
+ it 'raises an error if the conversion cannot be made' do
33
+ expect { subject.rate('USD', 'EUR') }.to raise_error Errors::ExchangeRateNotFound
34
+ end
35
+
36
+ def fake_exchange(rates)
37
+ FixedRates.new(rates: rates)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,7 @@
1
+ {
2
+ "success": false,
3
+ "error": {
4
+ "code": 104,
5
+ "info": "Your monthly usage limit has been reached. Please upgrade your subscription plan."
6
+ }
7
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "success": true,
3
+ "source": "USD",
4
+ "quotes": {
5
+ "USDARS": 27.110001,
6
+ "USDAUD": 1.346196
7
+ }
8
+ }
@@ -11,11 +11,11 @@ module Danconia
11
11
  end
12
12
  end
13
13
 
14
- context '#direct_rate' do
15
- it 'should find the rate for the pair' do
14
+ context 'rates' do
15
+ it 'returns a hash with rate by pair' do
16
16
  ExchangeRate.create! pair: 'USDEUR', rate: 2
17
- expect(subject.direct_rate('USD', 'EUR')).to eq 2
18
- expect(subject.direct_rate('USD', 'ARS')).to eq nil
17
+ ExchangeRate.create! pair: 'USDARS', rate: 40
18
+ expect(subject.rates).to eq('USDEUR' => 2, 'USDARS' => 40)
19
19
  end
20
20
  end
21
21
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: danconia
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.8
4
+ version: 0.2.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Emmanuel Nicolau
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-08-31 00:00:00.000000000 Z
11
+ date: 2020-09-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -110,7 +110,8 @@ dependencies:
110
110
  version: '0'
111
111
  description: Multi-currency money library backed by BigDecimal
112
112
  email: emmanicolau@gmail.com
113
- executables: []
113
+ executables:
114
+ - console
114
115
  extensions: []
115
116
  extra_rdoc_files: []
116
117
  files:
@@ -124,6 +125,7 @@ files:
124
125
  - LICENSE.txt
125
126
  - README.md
126
127
  - Rakefile
128
+ - bin/console
127
129
  - danconia.gemspec
128
130
  - examples/currency_layer.rb
129
131
  - examples/fixed_rates.rb
@@ -139,12 +141,15 @@ files:
139
141
  - lib/danconia/integrations/active_record.rb
140
142
  - lib/danconia/kernel.rb
141
143
  - lib/danconia/money.rb
144
+ - lib/danconia/pair.rb
142
145
  - lib/danconia/stores/active_record.rb
143
146
  - lib/danconia/stores/in_memory.rb
144
147
  - lib/danconia/test_helpers.rb
145
148
  - lib/danconia/version.rb
146
149
  - spec/danconia/exchanges/currency_layer_spec.rb
147
- - spec/danconia/exchanges/fixed_rates_spec.rb
150
+ - spec/danconia/exchanges/exchange_spec.rb
151
+ - spec/danconia/exchanges/fixtures/currency_layer/failure.json
152
+ - spec/danconia/exchanges/fixtures/currency_layer/success.json
148
153
  - spec/danconia/integrations/active_record_spec.rb
149
154
  - spec/danconia/money_spec.rb
150
155
  - spec/danconia/stores/active_record_spec.rb
@@ -176,7 +181,9 @@ specification_version: 4
176
181
  summary: Multi-currency money library backed by BigDecimal
177
182
  test_files:
178
183
  - spec/danconia/exchanges/currency_layer_spec.rb
179
- - spec/danconia/exchanges/fixed_rates_spec.rb
184
+ - spec/danconia/exchanges/exchange_spec.rb
185
+ - spec/danconia/exchanges/fixtures/currency_layer/failure.json
186
+ - spec/danconia/exchanges/fixtures/currency_layer/success.json
180
187
  - spec/danconia/integrations/active_record_spec.rb
181
188
  - spec/danconia/money_spec.rb
182
189
  - spec/danconia/stores/active_record_spec.rb
@@ -1,30 +0,0 @@
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