danconia 0.2.8 → 0.2.9

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 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