currency-rate 1.5.3 → 2.0.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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -3
  3. data/Gemfile.lock +68 -0
  4. data/README.md +2 -2
  5. data/Rakefile +12 -3
  6. data/currency-rate.gemspec +1 -1
  7. data/lib/adapter.rb +56 -22
  8. data/lib/adapters/crypto/binance_adapter.rb +1 -1
  9. data/lib/adapters/crypto/bitfinex_adapter.rb +3 -2
  10. data/lib/adapters/crypto/bitpay_adapter.rb +1 -1
  11. data/lib/adapters/crypto/bitstamp_adapter.rb +1 -1
  12. data/lib/adapters/crypto/coin_market_cap_adapter.rb +2 -4
  13. data/lib/adapters/crypto/coinbase_adapter.rb +3 -3
  14. data/lib/adapters/crypto/exmo_adapter.rb +1 -1
  15. data/lib/adapters/crypto/hit_BTC_adapter.rb +64 -0
  16. data/lib/adapters/crypto/huobi_adapter.rb +17 -9
  17. data/lib/adapters/crypto/kraken_adapter.rb +5 -5
  18. data/lib/adapters/crypto/localbitcoins_adapter.rb +1 -1
  19. data/lib/adapters/crypto/okcoin_adapter.rb +4 -6
  20. data/lib/adapters/crypto/paxful_adapter.rb +18 -0
  21. data/lib/adapters/crypto/poloniex_adapter.rb +33 -0
  22. data/lib/adapters/crypto/yadio_adapter.rb +2 -1
  23. data/lib/adapters/fiat/bonbast_adapter.rb +3 -5
  24. data/lib/adapters/fiat/coinmonitor_adapter.rb +4 -5
  25. data/lib/adapters/fiat/currency_layer_adapter.rb +2 -3
  26. data/lib/adapters/fiat/fixer_adapter.rb +3 -4
  27. data/lib/adapters/fiat/forge_adapter.rb +15 -6
  28. data/lib/adapters/fiat/free_forex_adapter.rb +7 -3
  29. data/lib/container.rb +203 -0
  30. data/lib/currency_rate.rb +20 -63
  31. data/lib/currency_rate/version.rb +1 -1
  32. data/lib/exceptions.rb +9 -0
  33. data/lib/storage/file_storage.rb +15 -10
  34. data/lib/utils/string_extensions.rb +19 -0
  35. metadata +12 -14
  36. data/api_keys.yml.sample +0 -4
  37. data/bin/rake +0 -29
  38. data/bin/rspec +0 -29
  39. data/lib/adapters/crypto/btc_china_adapter.rb +0 -11
  40. data/lib/adapters/crypto/btc_e_adapter.rb +0 -18
  41. data/lib/adapters/fiat/yahoo_adapter.rb +0 -35
  42. data/lib/configuration.rb +0 -29
  43. data/lib/fetcher.rb +0 -78
  44. data/lib/synchronizer.rb +0 -51
@@ -1,8 +1,8 @@
1
1
  module CurrencyRate
2
2
  class KrakenAdapter < Adapter
3
3
  SUPPORTED_CURRENCIES = %w(
4
- ADA BCH BSV BTC DASH EOS ETC ETH GNO LTC MLN
5
- NMC QTUM REP XDG XLM XMR XRP XTZ ZEC
4
+ ADA BCH BSV BTC DAI DASH EOS ETC ETH GNO LTC MLN
5
+ NMC QTUM REP USDC XDG XLM XMR XRP XTZ ZEC
6
6
  )
7
7
 
8
8
  ASSET_MAP = {
@@ -21,14 +21,14 @@ module CurrencyRate
21
21
 
22
22
  ANCHOR_CURRENCY = "BTC"
23
23
 
24
- FETCH_URL = "https://api.kraken.com/0/public/Ticker?pair=#{ %w(ADAXBT BCHXBT BSVXBT DASHXBT EOSXBT GNOXBT QTUMXBT XTZXBT XETCXXBT XETHXXBT XLTCXXBT XREPXXBT XXLMXXBT XXMRXXBT XXRPXXBT XZECXXBT XXBTZUSD).join(",") }"
24
+ FETCH_URL = "https://api.kraken.com/0/public/Ticker?pair=#{ %w(ADAXBT BCHXBT BSVXBT DASHXBT EOSXBT GNOXBT QTUMXBT XTZXBT XETCXXBT XETHXXBT XLTCXXBT XREPXXBT XXLMXXBT XXMRXXBT XXRPXXBT XZECXXBT XXBTZUSD XBTDAI XBTUSDC).join(",") }"
25
25
 
26
26
  def normalize(data)
27
- return nil unless super
27
+ return nil unless data = super
28
28
  data["result"].reduce({ "anchor" => ANCHOR_CURRENCY }) do |result, (pair, value)|
29
29
  key = ta(pair.sub(ta(ANCHOR_CURRENCY), ""))
30
30
 
31
- if key == "USD"
31
+ if %w(USD DAI USDC).include?(key)
32
32
  result[key] = BigDecimal(value["c"].first.to_s)
33
33
  else
34
34
  result[key] = 1 / BigDecimal(value["c"].first.to_s)
@@ -14,7 +14,7 @@ module CurrencyRate
14
14
  FETCH_URL = 'https://localbitcoins.com/bitcoinaverage/ticker-all-currencies/'
15
15
 
16
16
  def normalize(data)
17
- return nil unless super
17
+ return nil unless data = super
18
18
  data.reduce({ "anchor" => ANCHOR_CURRENCY }) do |result, (fiat, value)|
19
19
  result["#{fiat.upcase}"] = BigDecimal(value["rates"]["last"].to_s)
20
20
  result
@@ -1,16 +1,14 @@
1
1
  module CurrencyRate
2
2
  class OkcoinAdapter < Adapter
3
3
  FETCH_URL = {
4
- 'LTC_USD' => 'https://www.okcoin.com/api/v1/ticker.do?symbol=ltc_usd',
5
- 'BTC_USD' => 'https://www.okcoin.com/api/v1/ticker.do?symbol=btc_usd',
6
- 'LTC_CNY' => 'https://www.okcoin.cn/api/ticker.do?symbol=ltc_cny',
7
- 'BTC_CNY' => 'https://www.okcoin.cn/api/ticker.do?symbol=btc_cny'
4
+ 'LTC_USD' => 'https://www.okcoin.com/api/spot/v3/instruments/LTC-USD/ticker',
5
+ 'BTC_USD' => 'https://www.okcoin.com/api/spot/v3/instruments/BTC-USD/ticker',
8
6
  }
9
7
 
10
8
  def normalize(data)
11
- return nil unless super
9
+ return nil unless data = super
12
10
  data.reduce({}) do |result, (pair, value)|
13
- result[pair] = BigDecimal(value["ticker"]["last"].to_s)
11
+ result[pair] = BigDecimal(value["last"].to_s)
14
12
  result
15
13
  end
16
14
  end
@@ -0,0 +1,18 @@
1
+ module CurrencyRate
2
+ class PaxfulAdapter < Adapter
3
+ SUPPORTED_CURRENCIES = %w(USD)
4
+
5
+ ANCHOR_CURRENCY = "BTC"
6
+
7
+ FETCH_URL = "https://paxful.com/api/currency/btc"
8
+
9
+ def normalize(data)
10
+ return nil unless data = super
11
+
12
+ {
13
+ "anchor" => ANCHOR_CURRENCY,
14
+ "USD" => BigDecimal(data["price"].to_s)
15
+ }
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,33 @@
1
+ module CurrencyRate
2
+ class PoloniexAdapter < Adapter
3
+ SUPPORTED_CURRENCIES = %w(BTS DASH DOGE LTC NXT STR XEM XMR XRP USDT
4
+ ETH SC DCR LSK STEEM ETC REP ARDR ZEC STRAT
5
+ GNT ZRX CVC OMG GAS STORJ EOS SNT KNC BAT
6
+ LOOM QTUM USDC MANA BNT BCHABC BCHSV FOAM
7
+ NMR POLY LPT ATOM TRX ETHBNT LINK XTZ PAX
8
+ USDJ SNX MATIC MKR DAI NEO SWFTC FXC AVA
9
+ CHR BNB BUSD MDT XFIL LEND REN LRC WRX SXP
10
+ STPT SWAP EXE).freeze
11
+
12
+ ANCHOR_CURRENCY = "BTC".freeze
13
+
14
+ FETCH_URL = "https://poloniex.com/public?command=returnTicker".freeze
15
+
16
+ def normalize(data)
17
+ return nil unless data = super
18
+
19
+ data.each_with_object({ "anchor" => ANCHOR_CURRENCY }) do |(pair_name, pair_info), result|
20
+ next unless pair_name.include?(ANCHOR_CURRENCY)
21
+
22
+ key = pair_name.sub(ANCHOR_CURRENCY, "").sub("_", "")
23
+
24
+ result[key] =
25
+ if pair_name.index(ANCHOR_CURRENCY) == 0
26
+ 1 / BigDecimal(pair_info["last"])
27
+ else
28
+ BigDecimal(pair_info["last"])
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -7,10 +7,11 @@ module CurrencyRate
7
7
  FETCH_URL = "https://api.yadio.io/rate/BTC"
8
8
 
9
9
  def normalize(data)
10
- return nil unless super
10
+ return nil unless data = super
11
11
 
12
12
  {
13
13
  "anchor" => ANCHOR_CURRENCY,
14
+ "USD" => BigDecimal(data["usd"].to_s),
14
15
  "VES" => BigDecimal(data["rate"].to_s),
15
16
  }
16
17
  end
@@ -12,13 +12,11 @@ module CurrencyRate
12
12
  end
13
13
 
14
14
  def request(url)
15
-
16
- http_client = HTTP.timeout(connect:
17
- CurrencyRate.configuration.connect_timeout, read:
18
- CurrencyRate.configuration.read_timeout
15
+ http_client = HTTP.timeout(
16
+ connect: @container.connect_timeout,
17
+ read: @container.read_timeout
19
18
  )
20
19
  http_client.get(url).to_s
21
-
22
20
  end
23
21
 
24
22
  end
@@ -3,12 +3,11 @@ module CurrencyRate
3
3
  # No need to use it for fetching, just additional information about supported currencies
4
4
  SUPPORTED_CURRENCIES = %w(ARS)
5
5
  ANCHOR_CURRENCY = "USD"
6
- FETCH_URL = "https://ar.coinmonitor.info/api/dolar_ar/"
6
+ FETCH_URL = "https://ar.coinmonitor.info/data_ar.json"
7
7
 
8
8
  def normalize(data)
9
- return nil unless super
10
-
11
- { "anchor" => ANCHOR_CURRENCY }.merge({ SUPPORTED_CURRENCIES.first => BigDecimal(data["DOL_blue"].to_s) })
9
+ return nil unless data = super
10
+ { "anchor" => ANCHOR_CURRENCY }.merge({ SUPPORTED_CURRENCIES.first => BigDecimal(data["BTC_avr_ars"].to_s) })
12
11
  end
13
12
  end
14
- end
13
+ end
@@ -17,11 +17,10 @@ module CurrencyRate
17
17
  )
18
18
 
19
19
  ANCHOR_CURRENCY = "USD"
20
- FETCH_URL = "http://www.apilayer.net/api/live"
21
- API_KEY_PARAM = "access_key"
20
+ FETCH_URL = "http://apilayer.net/api/live?access_key=__API_KEY__"
22
21
 
23
22
  def normalize(data)
24
- return nil unless super
23
+ return nil unless data = super
25
24
  rates = { "anchor" => self.class::ANCHOR_CURRENCY }
26
25
  data["quotes"].each do |key, value|
27
26
  rates[key.sub(self.class::ANCHOR_CURRENCY, "")] = BigDecimal(value.to_s)
@@ -2,13 +2,12 @@ module CurrencyRate
2
2
  class FixerAdapter < Adapter
3
3
  # EUR is the only currency available as a base on free plan
4
4
  ANCHOR_CURRENCY = "EUR"
5
- FETCH_URL = "http://data.fixer.io/latest"
6
- API_KEY_PARAM = "access_key"
5
+ FETCH_URL = "http://data.fixer.io/api/latest?access_key=__API_KEY__&base=#{ANCHOR_CURRENCY}"
7
6
 
8
7
  def normalize(data)
9
- return nil unless super
8
+ return nil unless data = super
10
9
  rates = { "anchor" => ANCHOR_CURRENCY }
11
- data["rates"].each do |k, v|
10
+ data["rates"].each do |k,v|
12
11
  rates[k] = BigDecimal(v.to_s)
13
12
  end
14
13
  rates
@@ -1,22 +1,31 @@
1
1
  module CurrencyRate
2
2
  class ForgeAdapter < Adapter
3
3
  SUPPORTED_CURRENCIES = %w(
4
- JPY CHF CAD SEK NOK MXN ZAR TRY CNH EUR GBP AUD NZD XAU XAG
4
+ AED AFN ALL AMD ANG AOA ARE ARS AUD AUN AWG BAM BBD BDT BGN BHD BIF BMD
5
+ BND BOB BRI BRL BSD BTN BWP BYN BZD CAD CDF CHF CLF CLP CLY CNH CNY COP
6
+ CRC CUP CVE CYP CZK DJF DKK DOE DOP DZD EGP ETB EUR FJD FRN GBP GEL GHS
7
+ GMD GNF GTQ GYD HKD HNL HRK HTG HUF IDR ILS INR IQD IRR ISK JMD JOD JPY
8
+ KES KHR KMF KRU KRW KWD KYD KZT LAK LBP LFX LKR LRD LSL LTL LYD M5P MAD
9
+ MAL MDL MGA MKD MMK MOP MRU MTL MUR MVR MWK MXN MYR MZN NAD NBL NGN NIO
10
+ NOK NPR NSO NZD OMR OSO PAB PEN PGK PHP PKR PLN PYG QAR RON RSD RUB RWF
11
+ SAR SBD SCR SDG SEK SGD SHP SLL SOS SRD STN SVC SZL THB TJS TMT TND TOP
12
+ TRY TTD TWD TZS UAH UGX USD UYU UZS VES VND VRL VRN XAG XAGK XAU XAUK XCD
13
+ XDR XOF XPD XPDK XPF XPT XPTK YER ZAR ZMW ZWD
5
14
  )
6
15
 
7
16
  ANCHOR_CURRENCY = "USD"
8
- FETCH_URL = "https://forex.1forge.com/1.0.2/quotes?pairs=" + SUPPORTED_CURRENCIES.map { |c| "#{ANCHOR_CURRENCY}#{c}" }.join(",")
9
- API_KEY_PARAM = "api_key"
17
+ currency_pairs_request = SUPPORTED_CURRENCIES.map { |c| "#{ANCHOR_CURRENCY}/#{c}" }.join(",")
18
+ FETCH_URL = "https://api.1forge.com/quotes?pairs=#{currency_pairs_request}&api_key=__API_KEY__"
10
19
 
11
20
  def normalize(data)
12
- return nil unless super
21
+ return nil unless data = super
13
22
  rates = { "anchor" => self.class::ANCHOR_CURRENCY }
14
23
  data.each do |rate|
15
24
  if rate["error"]
16
- CurrencyRate.logger.error("Forge exchange returned error")
25
+ @container.logger.error("Forge exchange returned error")
17
26
  return nil
18
27
  end
19
- rates[rate["symbol"].sub(self.class::ANCHOR_CURRENCY, "")] = BigDecimal(rate["price"].to_s)
28
+ rates[rate["s"].sub("#{self.class::ANCHOR_CURRENCY}/", "")] = BigDecimal(rate["p"].to_s) if rate["p"]
20
29
  end
21
30
  rates
22
31
  end
@@ -1,4 +1,8 @@
1
1
  module CurrencyRate
2
+
3
+ # Beware, freeforexapi.com doesn't normally work and you need to place
4
+ # a small banner on your website so they'd list your websites IP address.
5
+ # More info here: http://freeforexapi.com/Home/Api
2
6
  class FreeForexAdapter < Adapter
3
7
  SUPPORTED_CURRENCIES = %w(
4
8
  AED AFN ALL AMD ANG AOA ARS ATS AUD AWG AZM AZN BAM BBD BDT
@@ -18,10 +22,10 @@ module CurrencyRate
18
22
  )
19
23
 
20
24
  ANCHOR_CURRENCY = "USD"
21
- FETCH_URL = "https://www.freeforexapi.com/api/live?pairs=" + SUPPORTED_CURRENCIES.map{|cur| "USD#{cur}"}.join(",")
25
+ FETCH_URL = "https://www.freeforexapi.com/api/live?pairs=" + SUPPORTED_CURRENCIES.map { |c| "USD#{c}"}.join(",")
22
26
 
23
27
  def normalize(data)
24
- return nil unless super
28
+ return nil unless data = super
25
29
  rates = { "anchor" => self.class::ANCHOR_CURRENCY }
26
30
  data["rates"].each do |pair, payload|
27
31
  rates[pair.sub(self.class::ANCHOR_CURRENCY, "")] = BigDecimal(payload["rate"].to_s)
@@ -29,4 +33,4 @@ module CurrencyRate
29
33
  rates
30
34
  end
31
35
  end
32
- end
36
+ end
@@ -0,0 +1,203 @@
1
+ class CurrencyRate::Container
2
+
3
+ attr_accessor :api_keys
4
+ attr_accessor :logger
5
+ attr_accessor :adapters
6
+ attr_accessor :connect_timeout
7
+ attr_accessor :read_timeout
8
+ attr_accessor :storage
9
+ attr_accessor :logger_callbacks
10
+ attr_accessor :limit_adapters_for_pairs
11
+
12
+ def initialize(
13
+ api_keys: {},
14
+ adapter_type: nil,
15
+ adapter_names: nil,
16
+ limit_adapters_for_pairs: {},
17
+ storage_settings: CurrencyRate.default_config[:storage_settings],
18
+ connect_timeout: CurrencyRate.default_config[:connect_timeout],
19
+ read_timeout: CurrencyRate.default_config[:read_timeout],
20
+ logger_settings: CurrencyRate.default_config[:logger_settings],
21
+ logger_callbacks: CurrencyRate.default_config[:logger_callbacks]
22
+ )
23
+
24
+ logger_settigns = CurrencyRate.default_config[:logger_settings].merge(logger_settings)
25
+ storage_settigns = CurrencyRate.default_config[:storage_settings].merge(storage_settings)
26
+
27
+ method(__method__).parameters.map do |_, name|
28
+ value = binding.local_variable_get(name)
29
+ instance_variable_set("@#{name}", value)
30
+ end
31
+
32
+ _logger_callbacks = {}
33
+ @logger_callbacks.each do |k,v|
34
+ _logger_callbacks[Logger::Severity.const_get(k.to_s.upcase)] = v
35
+ end
36
+ @logger_callbacks = _logger_callbacks
37
+
38
+ _limit_adapters_for_pairs = {}
39
+ @limit_adapters_for_pairs.each do |k,v|
40
+ _limit_adapters_for_pairs[k] = CurrencyRate.const_get(v.to_s.to_camel_case + "Adapter").instance
41
+ end
42
+ @limit_adapters_for_pairs = _limit_adapters_for_pairs
43
+
44
+ @storage = CurrencyRate.const_get(storage_settigns[:type].to_s.to_camel_case + "Storage").new(
45
+ path: storage_settings[:path],
46
+ container: self,
47
+ serializer: storage_settings[:serializer]
48
+ )
49
+
50
+ load_adapters!(names: adapter_names, type: adapter_type)
51
+ end
52
+
53
+ def method_missing(m, *args, &block)
54
+ if m.to_s.end_with? "_adapters"
55
+ self.send(:adapters, m[0..-10])
56
+ else
57
+ super
58
+ end
59
+ end
60
+
61
+ def logger
62
+ return @logger if @logger
63
+ @logger = Logger.new(@logger_settings[:device])
64
+ @logger.progname = "CurrencyRate"
65
+ @logger.level = @logger_settings[:level]
66
+ @logger.formatter = @logger_settings[:formatter] if @logger_settings[:formatter]
67
+ @logger
68
+ end
69
+
70
+ # This method doesn't make any requests. It merely reads normalized data
71
+ # from the selected storage. Contrary to what one might assume,
72
+ # it doesn't call CurrencyRate::Container.sync! if storage for a particular
73
+ # adapter doesn't exist - that's because those are two separate tasks
74
+ # and you might not want to make that external http(s) request even if
75
+ # the rates in storage are not found.
76
+ #
77
+ # It also uses a 3 different strategies to calculate rates before they are returned:
78
+ #
79
+ # 1. If only one adapter is specified and/or available, it will return the rate for the
80
+ # pair using the data from the storage for this particular adapter.
81
+ #
82
+ # 2. If two or more adapters are specified and available, it will return the average rate
83
+ # based on the the rates from all of those adapters.
84
+ #
85
+ # 3. If `strategy: :majority` flag is set and an odd number of adapters (say, 3)
86
+ # is passed and/or available it will look at the rates from all, separate them
87
+ # into two groups based on how close their rates are and return the average of the rates
88
+ # that are closest in the group that is the largest.
89
+ #
90
+ # It will also check @limit_adapters_for_pairs hash for the request pair
91
+ # and if the key exists it will exclude adapters that are not listed in the array
92
+ # under that key.
93
+ def get_rate(from, to, use_adapters=@adapter_names, strategy: :average)
94
+ adapters_for_pair = @limit_adapters_for_pairs["#{from}/#{to}"]
95
+ _adapters = adapters.select { |a| use_adapters.include?(a.name(:camel_case)) }
96
+ _adapters = _adapters.select { |a| adapters_for_pair.include?(a.name(:camel_case)) } if adapters_for_pair
97
+
98
+ warning_loggers = []
99
+ rates = _adapters.map do |a|
100
+ rate = [a.name(:camel_case), a.get_rate(from, to)]
101
+ warning_loggers.push -> { self.log(:warn, "No rate for pair #{from}/#{to} found in #{a.name(:camel_case)} adapter") if rate[1].nil? }
102
+ rate
103
+ end.select { |r| !r[1].nil? }
104
+
105
+ if rates.empty?
106
+ self.log(:error,
107
+ "No rate for pair #{from}/#{to} is found in any of the available adapters " +
108
+ "(#{_adapters.map { |a| a.name(:camel_case) }.join(", ")})"
109
+ )
110
+ else
111
+ warning_loggers.each { |wl| wl.call }
112
+ end
113
+
114
+ if rates.size == 1
115
+ rates[0][1]
116
+ else
117
+ if strategy == :majority && rates.size.odd?
118
+ rates.sort! { |r1, r2| r1[1] <=> r2[1] }
119
+
120
+ largest_discrepancy_rate_index = 0
121
+ last_discrepancy = 0
122
+
123
+ rates.each_with_index do |r,i|
124
+ if i > 0
125
+ discrepancy = r[1] - rates[i-1][1]
126
+ if discrepancy > last_discrepancy
127
+ last_discrepancy = discrepancy
128
+ largest_discrepancy_rate_index = i
129
+ end
130
+ end
131
+ end
132
+
133
+ rates_group_1 = rates[0...largest_discrepancy_rate_index]
134
+ rates_group_2 = rates[largest_discrepancy_rate_index..rates.size-1]
135
+ rates = [rates_group_1, rates_group_2].max { |g1,g2| g1.size <=> g2.size }
136
+ end
137
+ rates.inject(BigDecimal(0)) { |sum, i| sum + i[1] } / BigDecimal(rates.size)
138
+ end
139
+
140
+ end
141
+
142
+ def sync!
143
+ successfull = []
144
+ failed = []
145
+ @adapters.each do |adapter|
146
+ adapter_name = adapter.class.to_s.sub("CurrencyRate::", "")
147
+ begin
148
+ rates = adapter.fetch_rates
149
+ unless rates
150
+ self.log(:warn, "Trying to sync rates for #{adapter_name}, rates not found, but http(s) request did not return any error.")
151
+ failed << adapter_name
152
+ next
153
+ end
154
+ @storage.write(adapter_name.to_snake_case.sub("_adapter", ""), rates)
155
+ successfull << adapter_name
156
+ rescue StandardError => e
157
+ failed << adapter_name
158
+ self.log(:error, e)
159
+ next
160
+ end
161
+ end
162
+ [successfull, failed]
163
+ end
164
+
165
+ def log(level, message)
166
+ severity = Logger::Severity.const_get(level.to_s.upcase)
167
+ logger.send(level, message)
168
+ if @logger_callbacks[severity]
169
+ @logger_callbacks[severity].call(level, message)
170
+ else
171
+ @logger_callbacks.keys.sort.each do |k|
172
+ @logger_callbacks[k].call(level, message) && break if k < severity
173
+ end
174
+ end
175
+ end
176
+
177
+ private
178
+
179
+ def load_adapters!(names: nil, type: :all)
180
+ if names
181
+ names = [names] if names.kind_of?(String)
182
+ @adapters = names.map do |name|
183
+ CurrencyRate.const_get(name + "Adapter").instance
184
+ end
185
+ else
186
+ crypto_adapter_files = Dir[File.join CurrencyRate.root, "lib/adapters/crypto/*"]
187
+ fiat_adapter_files = Dir[File.join CurrencyRate.root, "lib/adapters/fiat/*"]
188
+ adapter_files = case type
189
+ when :crypto then crypto_adapter_files
190
+ when :fiat then fiat_adapter_files
191
+ else crypto_adapter_files + fiat_adapter_files
192
+ end
193
+ @adapters = adapter_files.map do |file|
194
+ CurrencyRate.const_get(File.basename(file, ".rb").to_camel_case).instance
195
+ end
196
+ end
197
+ @adapters.each { |a| a.container = self; a.api_key = @api_keys[a.name] }
198
+ @adapter_names = @adapters.map { |a| a.name(:camel_case) }
199
+ @adapters
200
+ end
201
+
202
+
203
+ end