danconia 0.2.8 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.envrc +2 -0
  3. data/.gitignore +1 -0
  4. data/.rubocop.yml +104 -0
  5. data/.ruby-version +1 -1
  6. data/.travis.yml +1 -1
  7. data/Gemfile.lock +63 -43
  8. data/README.md +17 -3
  9. data/bin/console +7 -0
  10. data/danconia.gemspec +5 -1
  11. data/examples/bna.rb +35 -0
  12. data/examples/currency_layer.rb +7 -3
  13. data/examples/fixed_rates.rb +1 -3
  14. data/examples/single_currency.rb +2 -3
  15. data/lib/danconia/currency.rb +2 -2
  16. data/lib/danconia/exchange.rb +47 -0
  17. data/lib/danconia/exchanges/bna.rb +61 -0
  18. data/lib/danconia/exchanges/currency_layer.rb +14 -2
  19. data/lib/danconia/exchanges/fixed_rates.rb +2 -4
  20. data/lib/danconia/integrations/active_record.rb +13 -14
  21. data/lib/danconia/money.rb +30 -24
  22. data/lib/danconia/pair.rb +15 -0
  23. data/lib/danconia/serializable.rb +17 -0
  24. data/lib/danconia/stores/active_record.rb +29 -6
  25. data/lib/danconia/stores/in_memory.rb +4 -9
  26. data/lib/danconia/version.rb +1 -1
  27. data/lib/danconia.rb +7 -4
  28. data/shell.nix +10 -0
  29. data/spec/danconia/exchanges/bna_spec.rb +52 -0
  30. data/spec/danconia/exchanges/currency_layer_spec.rb +28 -33
  31. data/spec/danconia/exchanges/exchange_spec.rb +54 -0
  32. data/spec/danconia/exchanges/fixtures/bna/home.html +124 -0
  33. data/spec/danconia/exchanges/fixtures/currency_layer/failure.json +7 -0
  34. data/spec/danconia/exchanges/fixtures/currency_layer/success.json +8 -0
  35. data/spec/danconia/integrations/active_record_spec.rb +25 -5
  36. data/spec/danconia/money_spec.rb +41 -17
  37. data/spec/danconia/serializable_spec.rb +16 -0
  38. data/spec/danconia/stores/active_record_spec.rb +81 -15
  39. data/spec/danconia/stores/in_memory_spec.rb +18 -0
  40. data/spec/spec_helper.rb +2 -1
  41. metadata +76 -14
  42. data/lib/danconia/exchanges/exchange.rb +0 -33
  43. data/spec/danconia/exchanges/fixed_rates_spec.rb +0 -30
@@ -1,16 +1,19 @@
1
1
  require 'bigdecimal'
2
2
  require 'danconia/errors/exchange_rate_not_found'
3
+ require 'danconia/serializable'
3
4
 
4
5
  module Danconia
5
6
  class Money
6
7
  include Comparable
7
- attr_reader :amount, :currency, :decimals, :exchange
8
+ include Serializable
8
9
 
9
- def initialize amount, currency_code = nil, decimals: 2, exchange: Danconia.config.default_exchange
10
- @decimals = decimals
10
+ attr_reader :amount, :currency, :decimals
11
+
12
+ def initialize(amount, currency_code = nil, decimals: 2, exchange_opts: {})
11
13
  @amount = parse amount
12
- @currency = Currency.find(currency_code || Danconia.config.default_currency, exchange)
13
- @exchange = exchange
14
+ @decimals = decimals
15
+ @currency = Currency.find(currency_code || Danconia.config.default_currency)
16
+ @exchange_opts = exchange_opts.reverse_merge(exchange: Danconia.config.default_exchange)
14
17
  end
15
18
 
16
19
  def format decimals: @decimals, **other_options
@@ -41,23 +44,22 @@ module Danconia
41
44
  end
42
45
 
43
46
  def <=> other
44
- other = other.exchange_to(currency).amount if other.is_a? Money
45
- amount <=> other
47
+ amount <=> amount_exchanged_to_this_currency(other)
46
48
  end
47
49
 
48
- def exchange_to other_currency, exchange: @exchange
49
- other_currency = other_currency.presence && Currency.find(other_currency, exchange) || currency
50
- rate = exchange.rate currency.code, other_currency.code
51
- clone_with amount * rate, other_currency, exchange
50
+ def exchange_to other_currency, **opts
51
+ opts = @exchange_opts.merge(opts)
52
+ other_currency = other_currency.presence && Currency.find(other_currency) || currency
53
+ rate = opts[:exchange].rate currency.code, other_currency.code, opts.except(:exchange)
54
+ clone_with amount * rate, other_currency, opts
52
55
  end
53
56
 
54
- %w(+ - * /).each do |op|
55
- class_eval <<-EOR, __FILE__, __LINE__ + 1
57
+ %w[+ - * /].each do |op|
58
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
56
59
  def #{op} other
57
- other = other.exchange_to(currency, exchange: @exchange).amount if other.is_a? Money
58
- clone_with(amount #{op} other)
60
+ clone_with(amount #{op} amount_exchanged_to_this_currency(other))
59
61
  end
60
- EOR
62
+ RUBY
61
63
  end
62
64
 
63
65
  def round *args
@@ -68,10 +70,6 @@ module Danconia
68
70
  (self * 100).round
69
71
  end
70
72
 
71
- def as_json *args
72
- amount.as_json *args
73
- end
74
-
75
73
  def default_currency?
76
74
  currency.code == Danconia.config.default_currency
77
75
  end
@@ -84,8 +82,8 @@ module Danconia
84
82
  end
85
83
  end
86
84
 
87
- def respond_to? method, *args
88
- super or @amount.respond_to?(method, *args)
85
+ def respond_to_missing? method, *args
86
+ @amount.respond_to?(method, *args) || super
89
87
  end
90
88
 
91
89
  private
@@ -94,8 +92,16 @@ module Danconia
94
92
  BigDecimal(object.to_s) rescue BigDecimal(0)
95
93
  end
96
94
 
97
- def clone_with amount, currency = @currency, exchange = @exchange
98
- Money.new amount, currency, decimals: decimals, exchange: exchange
95
+ def clone_with amount, currency = @currency, exchange_opts = @exchange_opts
96
+ Money.new amount, currency, decimals: @decimals, exchange_opts: exchange_opts
97
+ end
98
+
99
+ def amount_exchanged_to_this_currency other
100
+ if other.is_a? Money
101
+ other.exchange_to(currency, @exchange_opts).amount
102
+ else
103
+ other
104
+ end
99
105
  end
100
106
  end
101
107
  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
@@ -0,0 +1,17 @@
1
+ module Danconia
2
+ module Serializable
3
+ def marshal_dump
4
+ {amount: @amount, currency: @currency.code, decimals: @decimals}
5
+ end
6
+
7
+ def marshal_load serialized_money
8
+ @amount = serialized_money[:amount]
9
+ @currency = Currency.find(serialized_money[:currency])
10
+ @decimals = serialized_money[:decimals]
11
+ end
12
+
13
+ def as_json _options = {}
14
+ {amount: @amount, currency: @currency.code}
15
+ end
16
+ end
17
+ end
@@ -1,20 +1,43 @@
1
1
  module Danconia
2
2
  module Stores
3
+ # Store implementation that persist rates using ActiveRecord.
3
4
  class ActiveRecord
5
+ # @param unique_keys [Array] each save_rates will update records with this keys' values
6
+ # @param date_field [Symbol] used when storing daily rates
7
+ def initialize unique_keys: %i[pair], date_field: nil
8
+ @unique_keys = unique_keys
9
+ @date_field = date_field
10
+ end
11
+
12
+ # Creates or updates the rates by the `unique_keys` provided in the constructor.
13
+ #
14
+ # @param rates [Array] must be an array of maps.
4
15
  def save_rates rates
5
16
  ExchangeRate.transaction do
6
- rates.each do |pair, rate|
7
- ExchangeRate.where(pair: pair).first_or_initialize.update rate: rate
17
+ rates.each do |fields|
18
+ ExchangeRate
19
+ .where(fields.slice(*@unique_keys))
20
+ .first_or_initialize
21
+ .update(fields.slice(*ExchangeRate.column_names.map(&:to_sym)))
8
22
  end
9
23
  end
10
24
  end
11
25
 
12
- def direct_rate from, to
13
- ExchangeRate.find_by(pair: [from, to].join)&.rate
26
+ # Returns an array of maps like the one it received.
27
+ def rates **filters
28
+ ExchangeRate.where(process_filters(filters)).map { |er| er.attributes.symbolize_keys }
14
29
  end
15
30
 
16
- def rates
17
- Hash[ExchangeRate.all.map { |er| [er.pair, er.rate] }]
31
+ private
32
+
33
+ def process_filters filters
34
+ if @date_field
35
+ param = filters.delete(@date_field) || Date.today
36
+ last_record = ExchangeRate.where(filters).where("#{@date_field} <= ?", param).order(@date_field => :desc).first
37
+ filters.merge(@date_field => (last_record[@date_field] if last_record))
38
+ else
39
+ filters
40
+ end
18
41
  end
19
42
  end
20
43
 
@@ -1,19 +1,14 @@
1
1
  module Danconia
2
2
  module Stores
3
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
4
  def save_rates rates
12
5
  @rates = rates
13
6
  end
14
7
 
15
- def direct_rate from, to
16
- @rates[[from, to].join]
8
+ def rates **filters
9
+ @rates.select do |r|
10
+ filters.all? { |k, v| r[k] == v }
11
+ end
17
12
  end
18
13
  end
19
14
  end
@@ -1,3 +1,3 @@
1
1
  module Danconia
2
- VERSION = '0.2.8'
2
+ VERSION = '0.4.0'
3
3
  end
data/lib/danconia.rb CHANGED
@@ -1,11 +1,14 @@
1
+ require 'date'
2
+
3
+ require 'active_support'
4
+ require 'active_support/core_ext/object/blank'
5
+ require 'active_support/core_ext/hash/reverse_merge'
6
+
1
7
  require 'danconia/version'
2
8
  require 'danconia/config'
3
9
  require 'danconia/currency'
4
10
  require 'danconia/money'
5
11
  require 'danconia/kernel'
6
- require 'danconia/integrations/active_record'
7
- require 'danconia/exchanges/exchange'
12
+ require 'danconia/exchange'
8
13
  require 'danconia/exchanges/fixed_rates'
9
- require 'danconia/exchanges/currency_layer'
10
14
  require 'danconia/stores/in_memory'
11
- require 'danconia/stores/active_record'
data/shell.nix ADDED
@@ -0,0 +1,10 @@
1
+ with import <nixpkgs> {};
2
+ mkShell {
3
+ nativeBuildInputs = [
4
+ ruby
5
+ sqlite
6
+ ];
7
+ shellHook = ''
8
+ gem list -i '^bundler$' -v 1.17.3 >/dev/null || gem install bundler --version=1.17.3 --no-document
9
+ '';
10
+ }
@@ -0,0 +1,52 @@
1
+ require 'danconia/exchanges/bna'
2
+
3
+ module Danconia
4
+ module Exchanges
5
+ describe BNA do
6
+ subject { BNA.new }
7
+
8
+ context 'fetch_rates' do
9
+ it 'extracts the rates from the html' do
10
+ stub_request(:get, 'https://www.bna.com.ar/Personas').to_return body: fixture('home.html')
11
+ rates = subject.fetch_rates
12
+
13
+ expect(rates.select { |r| r[:rate_type] == 'billetes' }).to eq [
14
+ {pair: 'USDARS', rate: 78.25, date: Date.new(2020, 9, 1), rate_type: 'billetes'},
15
+ {pair: 'EURARS', rate: 89, date: Date.new(2020, 9, 1), rate_type: 'billetes'},
16
+ {pair: 'BRLARS', rate: 14.5, date: Date.new(2020, 9, 1), rate_type: 'billetes'}
17
+ ]
18
+
19
+ expect(rates.select { |r| r[:rate_type] == 'divisas' }).to eq [
20
+ {pair: 'USDARS', rate: 74.18, date: Date.new(2020, 8, 31), rate_type: 'divisas'},
21
+ {pair: 'EURARS', rate: 88.6822, date: Date.new(2020, 8, 31), rate_type: 'divisas'}
22
+ ]
23
+ end
24
+
25
+ it 'raise error if cannot parse the document' do
26
+ stub_request(:get, 'https://www.bna.com.ar/Personas').to_return body: 'some invalid html'
27
+ expect { subject.fetch_rates }.to raise_error Errors::APIError
28
+ end
29
+
30
+ def fixture file
31
+ File.read("#{__dir__}/fixtures/bna/#{file}")
32
+ end
33
+ end
34
+
35
+ context 'rates' do
36
+ it 'pass the params to the store and converts the array of rates back to hash' do
37
+ store = double('store')
38
+ expect(store).to receive(:rates)
39
+ .with(rate_type: 'billetes', date: nil)
40
+ .and_return([{pair: 'USDARS', rate: 3}])
41
+
42
+ exchange = BNA.new(store: store)
43
+ expect(exchange.rates(rate_type: 'billetes')).to eq 'USDARS' => 3
44
+ end
45
+
46
+ it 'rate_type is required' do
47
+ expect { BNA.new.rates }.to raise_error ArgumentError
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -1,55 +1,50 @@
1
- require 'spec_helper'
1
+ require 'danconia/exchanges/currency_layer'
2
2
 
3
3
  module Danconia
4
4
  module Exchanges
5
5
  describe CurrencyLayer do
6
- subject { CurrencyLayer.new access_key: '[KEY]' }
7
-
8
6
  context 'fetch_rates' do
7
+ subject { CurrencyLayer.new access_key: '[KEY]' }
8
+
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]')
11
+ .to_return(body: fixture('success.json'))
12
+
20
13
  expect(subject.fetch_rates).to eq 'USDARS' => 27.110001, 'USDAUD' => 1.346196
21
14
  end
22
15
 
23
16
  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
17
+ stub_request(:get, 'http://www.apilayer.net/api/live?access_key=[KEY]')
18
+ .to_return(body: fixture('failure.json'))
19
+
33
20
  expect { subject.fetch_rates }.to raise_error Errors::APIError
34
21
  end
22
+
23
+ def fixture file
24
+ File.read("#{__dir__}/fixtures/currency_layer/#{file}")
25
+ end
35
26
  end
36
27
 
37
28
  context 'update_rates!' do
38
29
  it 'fetches the rates and stores them' do
39
- expect(subject).to receive(:fetch_rates) { {'USDARS' => 3, 'USDAUD' => 4} }
40
- subject.update_rates!
41
- expect(subject.rates.size).to eq 2
42
- expect(subject.rate('USD', 'ARS')).to eq 3
43
- expect(subject.rate('USD', 'AUD')).to eq 4
30
+ store = double('store')
31
+ expect(store).to receive(:save_rates).with([{pair: 'USDARS', rate: 3}, {pair: 'USDAUD', rate: 4}])
32
+
33
+ exchange = CurrencyLayer.new(access_key: '...', store: store)
34
+ allow(exchange).to receive(:fetch_rates).and_return('USDARS' => 3, 'USDAUD' => 4)
35
+ exchange.update_rates!
44
36
  end
37
+ end
38
+
39
+ context 'rates' do
40
+ it 'converts the array from the store back to a map of pair to rates' do
41
+ store = double('store')
42
+ expect(store).to receive(:rates).and_return([{pair: 'USDARS', rate: 3}, {pair: 'USDAUD', rate: 4}])
45
43
 
46
- it 'if a rate already exists should update it' do
47
- subject.store.save_rates 'USDARS' => 3
48
- expect(subject).to receive(:fetch_rates) { {'USDARS' => 3.1} }
49
- subject.update_rates!
50
- expect(subject.rate('USD', 'ARS')).to eq 3.1
44
+ exchange = CurrencyLayer.new(access_key: '...', store: store)
45
+ expect(exchange.rates).to eq 'USDARS' => 3, 'USDAUD' => 4
51
46
  end
52
47
  end
53
48
  end
54
49
  end
55
- end
50
+ end
@@ -0,0 +1,54 @@
1
+ module Danconia
2
+ module Exchanges
3
+ describe Exchange do
4
+ context 'rate' do
5
+ it 'returns the exchange rate value for the supplied currencies' do
6
+ exchange = fake_exchange('USDEUR' => 3, 'USDARS' => 4)
7
+ expect(exchange.rate('USD', 'EUR')).to eq 3
8
+ expect(exchange.rate('USD', 'ARS')).to eq 4
9
+ end
10
+
11
+ it 'if the direct conversion is not found, tries to find the inverse' do
12
+ exchange = fake_exchange('USDEUR' => 3)
13
+ expect(exchange.rate('EUR', 'USD')).to be_within(0.00001).of(1.0 / 3)
14
+ end
15
+
16
+ it 'if not direct nor inverse conversion is found, tries to convert through USD' do
17
+ exchange = fake_exchange('USDEUR' => 3, 'USDARS' => 6)
18
+ expect(exchange.rate('EUR', 'ARS')).to be_within(0.00001).of 2
19
+ expect(exchange.rate('ARS', 'EUR')).to be_within(0.00001).of 0.5
20
+ end
21
+
22
+ it 'pairs can have a different common currency' do
23
+ exchange = fake_exchange('EURARS' => 3, 'BRLARS' => 1.5)
24
+ expect(exchange.rate('EUR', 'ARS')).to eq 3
25
+ expect(exchange.rate('ARS', 'EUR')).to be_within(0.00001).of(1.0 / 3)
26
+ expect(exchange.rate('BRL', 'ARS')).to eq 1.5
27
+ expect(exchange.rate('EUR', 'BRL')).to eq 3 / 1.5
28
+ end
29
+
30
+ it 'raises an error if the conversion cannot be made' do
31
+ expect { fake_exchange({}).rate('USD', 'EUR') }.to raise_error Errors::ExchangeRateNotFound
32
+ end
33
+
34
+ it 'should allow to pass options to filter the rates' do
35
+ exchange = Class.new(Exchange) do
36
+ def rates type:
37
+ case type
38
+ when 'divisa' then {'USDARS' => 7}
39
+ when 'billete' then {'USDARS' => 8}
40
+ end
41
+ end
42
+ end.new
43
+
44
+ expect(exchange.rate('USD', 'ARS', type: 'divisa')).to eq 7
45
+ expect(exchange.rate('USD', 'ARS', type: 'billete')).to eq 8
46
+ end
47
+
48
+ def fake_exchange(rates)
49
+ FixedRates.new(rates: rates)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,124 @@
1
+ <!DOCTYPE html>
2
+ <html lang="es" class="pc">
3
+
4
+ <body>
5
+ <div id="rightHome">
6
+ <div class="col-md-3">
7
+ <div class="tabSmall">
8
+ <ul class="nav nav-tabs">
9
+
10
+ <li class="active"><a href="#billetes" data-toggle="tab">Cotización Billetes</a>
11
+ <div class="arrow"></div>
12
+ </li>
13
+ <li><a href="#divisas" data-toggle="tab">Cotización Divisas</a>
14
+ <div class="arrow"></div>
15
+ </li>
16
+ </ul>
17
+
18
+ <div class="tab-content">
19
+
20
+ <div class="tab-pane fade in active" id="billetes">
21
+
22
+ <table class="table cotizacion">
23
+ <thead>
24
+ <tr>
25
+ <th class="fechaCot">1/9/2020</th>
26
+ <th>Compra</th>
27
+ <th>Venta</th>
28
+ </tr>
29
+ </thead>
30
+ <tbody>
31
+
32
+ <tr>
33
+ <td class="tit">Dolar U.S.A</td>
34
+ <td>73,2500</td>
35
+ <td>78,2500</td>
36
+ </tr>
37
+ <tr>
38
+ <td class="tit">Euro</td>
39
+ <td>84,0000</td>
40
+ <td>89,0000</td>
41
+ </tr>
42
+ <tr>
43
+ <td class="tit">Real *</td>
44
+ <td>1250,0000</td>
45
+ <td>1450,0000</td>
46
+ </tr>
47
+
48
+ </tbody>
49
+ </table>
50
+ <a href="#" class="link-cotizacion" data-toggle="modal" data-target="#modalHistorico" id="buttonHistoricoBilletes">Ver histórico</a>
51
+ <div class="legal">Hora Actualización: 10:40</div>
52
+ <div class="legal">(*) cotización cada 100 unidades.</div>
53
+
54
+ </div>
55
+
56
+ <div class="tab-pane fade" id="divisas">
57
+ <table class="table cotizacion">
58
+ <thead>
59
+ <tr>
60
+ <th class="fechaCot">31/8/2020</th>
61
+ <th>Compra</th>
62
+ <th>Venta</th>
63
+ </tr>
64
+ </thead>
65
+ <tbody>
66
+
67
+ <tr>
68
+ <td class="tit">Dolar U.S.A</td>
69
+ <td>73.9800</td>
70
+ <td>74.1800</td>
71
+ </tr>
72
+ <tr>
73
+ <td class="tit">Libra Esterlina</td>
74
+ <td>98.8743</td>
75
+ <td>99.3641</td>
76
+ </tr>
77
+ <tr>
78
+ <td class="tit">Euro</td>
79
+ <td>88.2581</td>
80
+ <td>88.6822</td>
81
+ </tr>
82
+ <tr>
83
+ <td class="tit">Franco Suizos *</td>
84
+ <td>8188.4905</td>
85
+ <td>8221.8519</td>
86
+ </tr>
87
+ <tr>
88
+ <td class="tit">YENES *</td>
89
+ <td>69.8238</td>
90
+ <td>70.1124</td>
91
+ </tr>
92
+ <tr>
93
+ <td class="tit">Dolares Canadienses *</td>
94
+ <td>5675.3160</td>
95
+ <td>5698.6457</td>
96
+ </tr>
97
+ <tr>
98
+ <td class="tit">Coronas Danesas *</td>
99
+ <td>1184.4394</td>
100
+ <td>1195.0806</td>
101
+ </tr>
102
+ <tr>
103
+ <td class="tit">Coronas Noruegas *</td>
104
+ <td>845.1963</td>
105
+ <td>854.8381</td>
106
+ </tr>
107
+ <tr>
108
+ <td class="tit">Coronas Suecas *</td>
109
+ <td>853.8596</td>
110
+ <td>863.8369</td>
111
+ </tr>
112
+
113
+ </tbody>
114
+ </table>
115
+ <a href="#" class="link-cotizacion" data-toggle="modal" data-target="#modalHistorico" id="buttonHistoricoMonedas">Ver histórico</a>
116
+ <div class="legal">(*) cotización cada 100 unidades.</div>
117
+ <div class="legal leyenda">El tipo de cambio de cierre de divisa es suministrado al público a fines informativos, como referencia de la cotización de la divisa en el mercado mayorista al final de cada día.</div>
118
+ </div>
119
+ </div>
120
+ </div>
121
+ </div>
122
+ </body>
123
+
124
+ </html>
@@ -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
+ }
@@ -1,12 +1,10 @@
1
- require 'spec_helper'
2
-
3
1
  module Danconia
4
2
  describe Integrations::ActiveRecord, active_record: true do
5
3
  context 'single currency' do
6
4
  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
5
+ expect(Product.new(price: 1.536).read_attribute(:price)).to eq 1.54
6
+ expect(Product.new(price: nil).read_attribute(:price)).to eq nil
7
+ expect(Product.new(price: Money(3)).read_attribute(:price)).to eq 3
10
8
  end
11
9
 
12
10
  it 'getter' do
@@ -34,6 +32,28 @@ module Danconia
34
32
  end
35
33
  end
36
34
 
35
+ context 'exchange options support' do
36
+ let(:exchange) do
37
+ Class.new(Exchange) do
38
+ def rates rate_type:
39
+ case rate_type
40
+ when 'divisa' then {'USDARS' => 7}
41
+ when 'billete' then {'USDARS' => 8}
42
+ end
43
+ end
44
+ end.new
45
+ end
46
+
47
+ it 'allows to specify options that will be pass to the exchange when exchanging to other currencies' do
48
+ klass = Class.new(ActiveRecord::Base) do
49
+ self.table_name = 'products'
50
+ money :price, rate_type: 'divisa'
51
+ end
52
+
53
+ expect(klass.new(price: Money(10, 'USD')).price.exchange_to('ARS', exchange: exchange)).to eq Money(70, 'ARS')
54
+ end
55
+ end
56
+
37
57
  class Product < ActiveRecord::Base
38
58
  money :price, :tax, :discount, :cost
39
59
  end