danconia 0.2.5 → 0.3.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.
- checksums.yaml +4 -4
- data/.rubocop.yml +104 -0
- data/.travis.yml +4 -0
- data/Gemfile.lock +40 -17
- data/README.md +16 -2
- data/bin/console +7 -0
- data/danconia.gemspec +5 -1
- data/examples/bna.rb +35 -0
- data/examples/currency_layer.rb +8 -4
- data/examples/fixed_rates.rb +1 -3
- data/examples/single_currency.rb +2 -3
- data/lib/danconia.rb +6 -4
- data/lib/danconia/currency.rb +2 -2
- data/lib/danconia/exchange.rb +47 -0
- data/lib/danconia/exchanges/bna.rb +61 -0
- data/lib/danconia/exchanges/currency_layer.rb +14 -2
- data/lib/danconia/exchanges/fixed_rates.rb +2 -4
- data/lib/danconia/integrations/active_record.rb +13 -14
- data/lib/danconia/money.rb +25 -24
- data/lib/danconia/pair.rb +15 -0
- data/lib/danconia/stores/active_record.rb +29 -6
- data/lib/danconia/stores/in_memory.rb +4 -9
- data/lib/danconia/version.rb +1 -1
- data/spec/danconia/exchanges/bna_spec.rb +36 -0
- data/spec/danconia/exchanges/currency_layer_spec.rb +28 -33
- data/spec/danconia/exchanges/exchange_spec.rb +54 -0
- data/spec/danconia/exchanges/fixtures/bna/home.html +124 -0
- data/spec/danconia/exchanges/fixtures/currency_layer/failure.json +7 -0
- data/spec/danconia/exchanges/fixtures/currency_layer/success.json +8 -0
- data/spec/danconia/integrations/active_record_spec.rb +25 -5
- data/spec/danconia/money_spec.rb +57 -15
- data/spec/danconia/stores/active_record_spec.rb +81 -15
- data/spec/danconia/stores/in_memory_spec.rb +18 -0
- data/spec/spec_helper.rb +2 -1
- metadata +67 -9
- data/lib/danconia/exchanges/exchange.rb +0 -31
- data/spec/danconia/exchanges/fixed_rates_spec.rb +0 -30
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d059c26cfe3dfb8fdd9da2a14f14d4207f49d39e57a8ac11624e4069f1612632
|
4
|
+
data.tar.gz: 2a7022742e0c9fdb318d57bf916828c18eab1c040aa7ec571912e243aef14028
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6c3ad4d5a0d58ca3fb6be34d557dfec5ef0578adf627edc2de3c7cee8b68eab84aafcebbf58c59e5622974dc8b2b7e609dd58e1d7dcf502eacd2090610ed7b4a
|
7
|
+
data.tar.gz: 439f6bb772fdef77bc221c6e4167a053b6366ff39fe8b20c3b6f81393901bc7c8364c1c326e357c0d2160ecd7bb50734cbdf1ae37eeb169bd2232b93e13e5181
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
Style/Documentation:
|
2
|
+
Enabled: false
|
3
|
+
|
4
|
+
Style/MethodDefParentheses:
|
5
|
+
Enabled: false
|
6
|
+
|
7
|
+
Style/ParallelAssignment:
|
8
|
+
Enabled: false
|
9
|
+
|
10
|
+
Style/FrozenStringLiteralComment:
|
11
|
+
Enabled: false
|
12
|
+
|
13
|
+
Style/FormatStringToken:
|
14
|
+
Enabled: false
|
15
|
+
|
16
|
+
Style/EmptyMethod:
|
17
|
+
Enabled: false
|
18
|
+
|
19
|
+
Style/GuardClause:
|
20
|
+
Enabled: false
|
21
|
+
|
22
|
+
Style/CommentAnnotation:
|
23
|
+
Enabled: false
|
24
|
+
|
25
|
+
Style/AsciiComments:
|
26
|
+
Enabled: false
|
27
|
+
|
28
|
+
Style/StabbyLambdaParentheses:
|
29
|
+
EnforcedStyle: require_no_parentheses
|
30
|
+
|
31
|
+
Style/MultilineIfModifier:
|
32
|
+
Enabled: false
|
33
|
+
|
34
|
+
Style/SafeNavigation:
|
35
|
+
Enabled: false
|
36
|
+
|
37
|
+
Style/FormatString:
|
38
|
+
Enabled: false
|
39
|
+
|
40
|
+
Style/AndOr:
|
41
|
+
Enabled: false
|
42
|
+
|
43
|
+
Style/Next:
|
44
|
+
Enabled: false
|
45
|
+
|
46
|
+
Style/NumericPredicate:
|
47
|
+
Enabled: false
|
48
|
+
|
49
|
+
Style/SymbolArray:
|
50
|
+
Enabled: false
|
51
|
+
|
52
|
+
Style/MutableConstant:
|
53
|
+
Enabled: false
|
54
|
+
|
55
|
+
Style/RescueModifier:
|
56
|
+
Enabled: false
|
57
|
+
|
58
|
+
Layout/LineLength:
|
59
|
+
Max: 120
|
60
|
+
|
61
|
+
Layout/SpaceInsideHashLiteralBraces:
|
62
|
+
Enabled: false
|
63
|
+
|
64
|
+
Layout/MultilineMethodCallIndentation:
|
65
|
+
Enabled: false
|
66
|
+
|
67
|
+
Layout/TrailingEmptyLines:
|
68
|
+
Enabled: false
|
69
|
+
|
70
|
+
Layout/FirstArrayElementIndentation:
|
71
|
+
EnforcedStyle: consistent
|
72
|
+
|
73
|
+
Layout/ParameterAlignment:
|
74
|
+
EnforcedStyle: with_fixed_indentation
|
75
|
+
|
76
|
+
Layout/SpaceInLambdaLiteral:
|
77
|
+
Enabled: false
|
78
|
+
|
79
|
+
Layout/EmptyLineAfterGuardClause:
|
80
|
+
Enabled: false
|
81
|
+
|
82
|
+
Metrics:
|
83
|
+
Enabled: false
|
84
|
+
|
85
|
+
Bundler:
|
86
|
+
Enabled: false
|
87
|
+
|
88
|
+
Lint/AssignmentInCondition:
|
89
|
+
Enabled: false
|
90
|
+
|
91
|
+
Lint/ShadowingOuterLocalVariable:
|
92
|
+
Enabled: false
|
93
|
+
|
94
|
+
Lint/AmbiguousRegexpLiteral:
|
95
|
+
Enabled: false
|
96
|
+
|
97
|
+
Lint/AmbiguousOperator:
|
98
|
+
Enabled: false
|
99
|
+
|
100
|
+
Lint/AmbiguousBlockAssociation:
|
101
|
+
Enabled: false
|
102
|
+
|
103
|
+
Naming/AccessorMethodName:
|
104
|
+
Enabled: false
|
data/.travis.yml
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,28 +1,28 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
danconia (0.
|
5
|
-
|
4
|
+
danconia (0.3.0)
|
5
|
+
activesupport (>= 4.0)
|
6
6
|
|
7
7
|
GEM
|
8
8
|
remote: https://rubygems.org/
|
9
9
|
specs:
|
10
|
-
activemodel (
|
11
|
-
activesupport (=
|
12
|
-
activerecord (
|
13
|
-
activemodel (=
|
14
|
-
activesupport (=
|
15
|
-
|
16
|
-
activesupport (5.2.0)
|
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)
|
17
16
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
18
17
|
i18n (>= 0.7, < 2)
|
19
18
|
minitest (~> 5.1)
|
20
19
|
tzinfo (~> 1.1)
|
20
|
+
zeitwerk (~> 2.2, >= 2.2.2)
|
21
21
|
addressable (2.5.2)
|
22
22
|
public_suffix (>= 2.0.2, < 4.0)
|
23
|
-
|
23
|
+
ast (2.4.1)
|
24
24
|
coderay (1.1.2)
|
25
|
-
concurrent-ruby (1.
|
25
|
+
concurrent-ruby (1.1.7)
|
26
26
|
crack (0.4.3)
|
27
27
|
safe_yaml (~> 1.0.0)
|
28
28
|
diff-lcs (1.3)
|
@@ -43,27 +43,36 @@ GEM
|
|
43
43
|
guard-compat (~> 1.1)
|
44
44
|
rspec (>= 2.99.0, < 4.0)
|
45
45
|
hashdiff (0.3.7)
|
46
|
-
i18n (1.
|
46
|
+
i18n (1.8.5)
|
47
47
|
concurrent-ruby (~> 1.0)
|
48
|
+
jaro_winkler (1.5.4)
|
48
49
|
listen (3.1.5)
|
49
50
|
rb-fsevent (~> 0.9, >= 0.9.4)
|
50
51
|
rb-inotify (~> 0.9, >= 0.9.7)
|
51
52
|
ruby_dep (~> 1.2)
|
52
53
|
lumberjack (1.0.13)
|
53
54
|
method_source (0.9.0)
|
54
|
-
|
55
|
+
mini_portile2 (2.4.0)
|
56
|
+
minitest (5.14.2)
|
55
57
|
nenv (0.3.0)
|
58
|
+
nokogiri (1.10.10)
|
59
|
+
mini_portile2 (~> 2.4.0)
|
56
60
|
notiffany (0.1.1)
|
57
61
|
nenv (~> 0.1)
|
58
62
|
shellany (~> 0.0)
|
63
|
+
parallel (1.19.2)
|
64
|
+
parser (2.7.1.4)
|
65
|
+
ast (~> 2.4.1)
|
59
66
|
pry (0.11.3)
|
60
67
|
coderay (~> 1.1.0)
|
61
68
|
method_source (~> 0.9.0)
|
62
69
|
public_suffix (3.0.2)
|
63
|
-
|
70
|
+
rainbow (3.0.0)
|
71
|
+
rake (13.0.1)
|
64
72
|
rb-fsevent (0.10.3)
|
65
73
|
rb-inotify (0.9.10)
|
66
74
|
ffi (>= 0.5.0, < 2)
|
75
|
+
rexml (3.2.4)
|
67
76
|
rspec (3.7.0)
|
68
77
|
rspec-core (~> 3.7.0)
|
69
78
|
rspec-expectations (~> 3.7.0)
|
@@ -77,30 +86,44 @@ GEM
|
|
77
86
|
diff-lcs (>= 1.2.0, < 2.0)
|
78
87
|
rspec-support (~> 3.7.0)
|
79
88
|
rspec-support (3.7.1)
|
89
|
+
rubocop (0.80.1)
|
90
|
+
jaro_winkler (~> 1.5.1)
|
91
|
+
parallel (~> 1.10)
|
92
|
+
parser (>= 2.7.0.1)
|
93
|
+
rainbow (>= 2.2.2, < 4.0)
|
94
|
+
rexml
|
95
|
+
ruby-progressbar (~> 1.7)
|
96
|
+
unicode-display_width (>= 1.4.0, < 1.7)
|
97
|
+
ruby-progressbar (1.10.1)
|
80
98
|
ruby_dep (1.5.0)
|
81
99
|
safe_yaml (1.0.4)
|
82
100
|
shellany (0.0.1)
|
83
|
-
sqlite3 (1.
|
101
|
+
sqlite3 (1.4.1)
|
84
102
|
thor (0.20.0)
|
85
103
|
thread_safe (0.3.6)
|
86
|
-
tzinfo (1.2.
|
104
|
+
tzinfo (1.2.7)
|
87
105
|
thread_safe (~> 0.1)
|
106
|
+
unicode-display_width (1.6.1)
|
88
107
|
webmock (3.4.2)
|
89
108
|
addressable (>= 2.3.6)
|
90
109
|
crack (>= 0.3.2)
|
91
110
|
hashdiff
|
111
|
+
zeitwerk (2.4.0)
|
92
112
|
|
93
113
|
PLATFORMS
|
94
114
|
ruby
|
95
115
|
|
96
116
|
DEPENDENCIES
|
117
|
+
activerecord (>= 4.0)
|
97
118
|
danconia!
|
98
119
|
guard
|
99
120
|
guard-rspec
|
121
|
+
nokogiri
|
100
122
|
rake
|
101
123
|
rspec
|
124
|
+
rubocop (~> 0.80.0)
|
102
125
|
sqlite3
|
103
126
|
webmock
|
104
127
|
|
105
128
|
BUNDLED WITH
|
106
|
-
1.
|
129
|
+
1.17.3
|
data/README.md
CHANGED
@@ -1,9 +1,19 @@
|
|
1
1
|
# Danconia
|
2
2
|
|
3
|
-
A very simple money library for Ruby, backed by BigDecimal
|
3
|
+
A very simple money library for Ruby, backed by BigDecimal, with multi-currency support.
|
4
4
|
|
5
5
|
[](https://travis-ci.org/eeng/danconia)
|
6
6
|
|
7
|
+
## Features
|
8
|
+
|
9
|
+
* Backed by BigDecimal (no conversion to cents is done, i.e. "infinite precision")
|
10
|
+
* Multi-currency support
|
11
|
+
* Pluggable exchange rates services:
|
12
|
+
- [CurrencyLayer API](https://currencylayer.com/)
|
13
|
+
- [BNA](https://www.bna.com.ar/)
|
14
|
+
- FixedRates (for testing use)
|
15
|
+
* Pluggable stores for persisting the exchange rates
|
16
|
+
|
7
17
|
## Installation
|
8
18
|
|
9
19
|
```ruby
|
@@ -29,10 +39,12 @@ Please refer to `examples/single_currency.rb` for some configuration options.
|
|
29
39
|
|
30
40
|
## Multi-Currency Support
|
31
41
|
|
32
|
-
To handle multiple currencies you need to configure an `Exchange` in order to fetch the rates. For example, with
|
42
|
+
To handle multiple currencies you need to configure an `Exchange` in order to fetch the rates. For example, with CurrencyLayer:
|
33
43
|
|
34
44
|
```ruby
|
35
45
|
# This can be placed in a Rails initializer
|
46
|
+
require 'danconia/exchanges/currency_layer'
|
47
|
+
|
36
48
|
Danconia.configure do |config|
|
37
49
|
config.default_exchange = Danconia::Exchanges::CurrencyLayer.new(access_key: '...')
|
38
50
|
end
|
@@ -56,6 +68,8 @@ By default, rates are stored in memory, but you can supply a store in the exchan
|
|
56
68
|
Given a `products` table with a decimal column `price` and a string column `price_currency` (optional), then you can use the `money` class method to automatically convert it to Money:
|
57
69
|
|
58
70
|
```ruby
|
71
|
+
require 'danconia/integrations/active_record'
|
72
|
+
|
59
73
|
class Product < ActiveRecord::Base
|
60
74
|
money :price
|
61
75
|
end
|
data/bin/console
ADDED
data/danconia.gemspec
CHANGED
@@ -17,11 +17,15 @@ Gem::Specification.new do |gem|
|
|
17
17
|
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
18
18
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
19
19
|
gem.require_paths = ["lib"]
|
20
|
-
|
20
|
+
|
21
|
+
gem.add_dependency 'activesupport', '>= 4.0'
|
22
|
+
gem.add_development_dependency "activerecord", '>= 4.0'
|
21
23
|
gem.add_development_dependency "sqlite3"
|
22
24
|
gem.add_development_dependency "rake"
|
23
25
|
gem.add_development_dependency "guard"
|
24
26
|
gem.add_development_dependency "guard-rspec"
|
25
27
|
gem.add_development_dependency "rspec"
|
26
28
|
gem.add_development_dependency "webmock"
|
29
|
+
gem.add_development_dependency "nokogiri"
|
30
|
+
gem.add_development_dependency 'rubocop', '~> 0.80.0'
|
27
31
|
end
|
data/examples/bna.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# Example using BNA (Banco Nación de Argentina) and storing rates daily in ActiveRecord.
|
2
|
+
|
3
|
+
require 'danconia/integrations/active_record'
|
4
|
+
require 'danconia/exchanges/bna'
|
5
|
+
|
6
|
+
ActiveRecord::Base.logger = Logger.new(STDOUT)
|
7
|
+
ActiveRecord::Base.establish_connection adapter: 'sqlite3', database: ':memory:'
|
8
|
+
|
9
|
+
ActiveRecord::Schema.define do
|
10
|
+
# You can use this in a Rails migration
|
11
|
+
create_table :exchange_rates do |t|
|
12
|
+
t.date :date
|
13
|
+
t.string :pair, limit: 6
|
14
|
+
t.string :rate_type
|
15
|
+
t.decimal :rate, precision: 12, scale: 6
|
16
|
+
t.index [:date, :pair, :rate_type], unique: true
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
Danconia.configure do |config|
|
21
|
+
config.default_exchange = Danconia::Exchanges::BNA.new(
|
22
|
+
store: Danconia::Stores::ActiveRecord.new(unique_keys: %i[date pair rate_type], date_field: :date)
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Periodically call this method to keep the rates up to date
|
27
|
+
puts 'Updating rates...'
|
28
|
+
Danconia.config.default_exchange.update_rates!
|
29
|
+
|
30
|
+
# Uses the latest rate
|
31
|
+
puts Money(1, 'USD').exchange_to('ARS', rate_type: 'billetes').inspect
|
32
|
+
puts Money(1, 'USD').exchange_to('ARS', rate_type: 'divisas').inspect
|
33
|
+
|
34
|
+
# Raises Danconia::Errors::ExchangeRateNotFound as there is no rate for that date
|
35
|
+
# Money(1, 'USD').exchange_to('ARS', rate_type: 'billetes', date: Date.new(2000))
|
data/examples/currency_layer.rb
CHANGED
@@ -1,6 +1,10 @@
|
|
1
|
+
# Example using CurrencyLayer API and storing only the latests rates in ActiveRecord.
|
1
2
|
# Remember to supply your CurrencyLayer key in the ACCESS_KEY environment variable to run this example
|
2
|
-
require 'danconia'
|
3
3
|
|
4
|
+
require 'danconia/integrations/active_record'
|
5
|
+
require 'danconia/exchanges/currency_layer'
|
6
|
+
|
7
|
+
ActiveRecord::Base.logger = Logger.new(STDOUT)
|
4
8
|
ActiveRecord::Base.establish_connection adapter: 'sqlite3', database: ':memory:'
|
5
9
|
|
6
10
|
ActiveRecord::Schema.define do
|
@@ -19,8 +23,8 @@ Danconia.configure do |config|
|
|
19
23
|
)
|
20
24
|
end
|
21
25
|
|
22
|
-
# Periodically call this method to keep rates up to date
|
23
|
-
puts 'Updating
|
26
|
+
# Periodically call this method to keep the rates up to date
|
27
|
+
puts 'Updating rates...'
|
24
28
|
Danconia.config.default_exchange.update_rates!
|
25
29
|
|
26
|
-
puts Money(1, 'USD').exchange_to('EUR').inspect # => 0.854896 EUR
|
30
|
+
puts Money(1, 'USD').exchange_to('EUR').inspect # => 0.854896 EUR
|
data/examples/fixed_rates.rb
CHANGED
@@ -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
|
data/examples/single_currency.rb
CHANGED
@@ -1,8 +1,7 @@
|
|
1
|
-
|
1
|
+
# Example showing how to use a single currency.
|
2
2
|
|
3
3
|
# USD is the default currency if no configuration is provided
|
4
|
-
puts
|
5
|
-
puts (Money(10.25) / 2).to_s # => $5.13
|
4
|
+
puts Money(10.25).inspect # => 5.125 USD
|
6
5
|
|
7
6
|
# Lets switch to other currency
|
8
7
|
Danconia.configure do |config|
|
data/lib/danconia.rb
CHANGED
@@ -1,11 +1,13 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
require 'active_support'
|
4
|
+
require 'active_support/core_ext/object/blank'
|
5
|
+
|
1
6
|
require 'danconia/version'
|
2
7
|
require 'danconia/config'
|
3
8
|
require 'danconia/currency'
|
4
9
|
require 'danconia/money'
|
5
10
|
require 'danconia/kernel'
|
6
|
-
require 'danconia/
|
7
|
-
require 'danconia/exchanges/exchange'
|
11
|
+
require 'danconia/exchange'
|
8
12
|
require 'danconia/exchanges/fixed_rates'
|
9
|
-
require 'danconia/exchanges/currency_layer'
|
10
13
|
require 'danconia/stores/in_memory'
|
11
|
-
require 'danconia/stores/active_record'
|
data/lib/danconia/currency.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module Danconia
|
2
|
-
|
3
|
-
def self.find code
|
2
|
+
Currency = Struct.new(:code, :symbol, :description, keyword_init: true) do
|
3
|
+
def self.find code
|
4
4
|
return code if code.is_a? Currency
|
5
5
|
new Danconia.config.available_currencies.find { |c| c[:code] == code } || {code: code, symbol: '$'}
|
6
6
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'danconia/pair'
|
2
|
+
|
3
|
+
module Danconia
|
4
|
+
class Exchange
|
5
|
+
def rate from, to, **opts
|
6
|
+
return 1.0 if from == to
|
7
|
+
|
8
|
+
pair = Pair.new(from, to)
|
9
|
+
rates = direct_and_inverted_rates(opts)
|
10
|
+
rates[pair] or indirect_rate(pair, rates) or raise Errors::ExchangeRateNotFound.new(from, to)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Override this method in subclasses. Should return a map of pairs to rates.
|
14
|
+
# See `FixedRates` for an example implementation.
|
15
|
+
def rates **_opts
|
16
|
+
raise NotImplementedError
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
# Returns the original rates plus the inverted ones, to simplify rate finding logic.
|
22
|
+
# Also wraps the pair strings into Pair objects.
|
23
|
+
def direct_and_inverted_rates opts
|
24
|
+
rates(opts).each_with_object({}) do |(pair_str, rate), rs|
|
25
|
+
pair = Pair.parse(pair_str)
|
26
|
+
rs[pair] = rate
|
27
|
+
rs[pair.invert] ||= 1.0 / rate
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def indirect_rate ind_pair, rates
|
32
|
+
if (from_pair = rates.keys.detect { |pair| pair.from == ind_pair.from }) &&
|
33
|
+
(to_pair = rates.keys.detect { |pair| pair.to == ind_pair.to })
|
34
|
+
rates[from_pair] * rates[to_pair]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def array_of_rates_to_hash array
|
39
|
+
pairs = array.map { |er| er[:pair] }
|
40
|
+
if pairs.size != pairs.uniq.size
|
41
|
+
raise ArgumentError, "Exchange returned duplicate pairs. Maybe you forgot a filter?\n#{array}"
|
42
|
+
end
|
43
|
+
|
44
|
+
Hash[array.map { |er| er.values_at(:pair, :rate) }]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|