danconia 0.2.8 → 0.4.0

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