omni_exchange 0.2.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f688c61226d02342f3c6e2730982b059d46cf6033fa61f822477d332bf21fc6b
4
- data.tar.gz: 19d3020b4d6146634d6931e8ac72247e5d17072156bca230a75307c398b26c4c
3
+ metadata.gz: 79069dcd87cf3788896dbc2093d114028b94592743a5fea03559cfce974b4d5c
4
+ data.tar.gz: 03a24de2dcfe6dca9ee1c0532e40b8bab6addbc14c9e4863ef644789a0a24adb
5
5
  SHA512:
6
- metadata.gz: 44b9222aa670f9495d713b62b3a01115388c68e820cd24d699beab5b8df9a80a084a3f3613f9edac17c2e0e4e5fa642cd0bab6b466b87b56e25ee8b6ae88387e
7
- data.tar.gz: 704c9d03570dec02e8115bf224a354e1bcea4a85574167b826cbb33b9d7dccce7c88c404d18d76d58291b4c74709624f6cd888353e05ca0b12e86c2dac6709b6
6
+ metadata.gz: bb380807a18d884f6ebef61b911e9a5301f6704577a15b8b4fc75cc11a4e0337ee1c911ea01c9a8269f919f6df62f86225cc9270bf24c8cad753eed0cefa8e2d
7
+ data.tar.gz: f0909dabbacc96857512b405cc590431ca9df724f622d0e59d589c62e2856e2d6193e992370f4a06d52df2eff7c3f6d1acca1741f5d6c12c9be20c9736b6eef0
data/.gitignore CHANGED
@@ -14,3 +14,7 @@
14
14
  .env
15
15
 
16
16
  sandbox.rb
17
+ omni_exchange-0.1.0.gem
18
+ omni_exchange-0.2.0.gem
19
+ omni_exchange-1.1.0.gem
20
+ omni_exchange-1.1.1.gem
data/.rubocop_todo.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2022-06-24 08:14:59 UTC using RuboCop version 0.93.1.
3
+ # on 2022-06-30 01:49:03 UTC using RuboCop version 0.93.1.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
@@ -15,7 +15,7 @@ Metrics/AbcSize:
15
15
  # Configuration parameters: CountComments, CountAsOne, ExcludedMethods.
16
16
  # ExcludedMethods: refine
17
17
  Metrics/BlockLength:
18
- Max: 26
18
+ Max: 27
19
19
 
20
20
  # Offense count: 1
21
21
  Naming/AccessorMethodName:
data/Gemfile.lock CHANGED
@@ -1,19 +1,25 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- omni_exchange (0.2.0)
4
+ omni_exchange (1.2.0)
5
5
  faraday (= 0.17.4)
6
+ money (~> 6.13.1)
6
7
 
7
8
  GEM
8
9
  remote: https://rubygems.org/
9
10
  specs:
10
11
  ast (2.4.2)
11
12
  coderay (1.1.3)
13
+ concurrent-ruby (1.1.10)
12
14
  diff-lcs (1.5.0)
13
15
  dotenv (2.7.6)
14
16
  faraday (0.17.4)
15
17
  multipart-post (>= 1.2, < 3)
18
+ i18n (1.10.0)
19
+ concurrent-ruby (~> 1.0)
16
20
  method_source (1.0.0)
21
+ money (6.13.8)
22
+ i18n (>= 0.6.4, <= 2)
17
23
  multipart-post (2.2.3)
18
24
  parallel (1.22.1)
19
25
  parser (3.1.2.0)
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  OmniExchange converts currencies using up-to-the-minute foreign exchange rates.
4
4
 
5
- OmniExchange also supports fail-over logic and handles timeouts. In other words, if currency conversion isn't possible because an API data source cannot provide an exchange rate, OR if that data source times out, OmniExchange will retrieve exchange rate data from another API data source.
5
+ OmniExchange also supports fail-over logic and handles timeouts. In other words, if currency conversion isn't possible because an API data source cannot provide an exchange rate, OR if that data source times out, OmniExchange will retrieve exchange rate data seamlessly from another API data source.
6
6
 
7
7
  ## Installation
8
8
 
@@ -46,24 +46,34 @@ OmniExchange.configure do |config|
46
46
  end
47
47
  ```
48
48
 
49
- #### **Step 3) Convert Currency**
49
+ #### **Step 3) Convert Currency and/or Get An Exchange Rate**
50
50
 
51
- To convert currency, all you have to do is call `OmniExchange.exchange_currency()`. This method requires you to pass the following four named parameters:
52
- 1. amount: (Integer)the amount of the currency you want to convert. NOTE: OmniExchange will read this amount as being the smallest unit of a currency. In other words, if you pass `10` as the amount for USD, OmniExchange will read this as 10 cents, not 10 dollars.
51
+ To convert currency and/or get an exchange rate, all you have to do is call `OmniExchange.get_fx_data()`. This method requires you to pass the following four named parameters:
52
+ 1. amount: (Integer) the amount of the currency you want to convert. NOTE: OmniExchange will read this amount as being the smallest unit of a currency. In other words, if you pass `10` as the amount for USD, OmniExchange will read this as 10 cents, not 10 dollars.
53
53
  2. base_currency: (String) the ISO Currency Code of the currency that you're exchanging from. ie. 'USD', 'JPY'
54
54
  3. target_currency: (String) the ISO Currency Code of the currency that you're exchanging to. ie. 'EUR', 'KRW'
55
- 4. providers: (Array) the keys of the API providers that you want data from in order of preference. ie. [:xe, :open_exchange_rates]
55
+ 4. providers: (Array of Symbols) the keys of the API providers that you want data from in order of preference. ie. [:xe, :open_exchange_rates]
56
56
 
57
- [For the sake of precise calculation](https://www.bigbinary.com/blog/handling-money-in-ruby), you'll get back a BigDecimal. Simply call `.to_f` to the result if you'd like to see a number that is easier to read.
57
+ What you get back is a hash containing:
58
+ 1. converted_amount: (BigDecimal) the amount of money exchanged from the base currency to the target currency
59
+ 2. exchange_rate: (BigDecimal) the rate used to calculate the converted_amount
60
+ 3. provider_class: (Class) the name of the provider class that supplied the exchange_rate (ie. OmniExchange::OpenExhangeRates)
58
61
 
62
+ [For the sake of precise calculation](https://www.bigbinary.com/blog/handling-money-in-ruby), converted_amount and exchange_rate are BigDecimal. Simply call `.to_f` to the results if you'd like to see a number that is easier to read.
59
63
 
60
- Here is an example. Lets say I want to convert $10.00 US Dollars to Japanese Yen, and I want it converted using exchange rate data from Open Exchange Rates. If Open Exchange Rates fails, I'd like OmniExchange to try to use exchange rate data from Xe as a fallback.
64
+
65
+ Here is an example. Lets say I want to convert $1.00 US Dollar to Japanese Yen, and I want it converted using exchange rate data from Open Exchange Rates. If Open Exchange Rates fails, I'd like OmniExchange to try to use exchange rate data from Xe as a fallback.
61
66
 
62
67
  ```ruby
63
- USD_to_JPY = OmniExchange.exchange_currency(amount: 1000, base_currency: "USD", target_currency: "JPY", providers: [:open_exchange_rates, :xe])
68
+ USD_to_JPY = OmniExchange.get_fx_data(amount: 100, base_currency: 'USD', target_currency: 'JPY', providers: [:open_exchange_rates, :xe])
69
+
70
+ puts USD_to_JPY # => { :converted_amount=>0.13566633333e3, :exchange_rate=>0.13566633333e1, :provider_class=>OmniExchange::OpenExchangeRates }
71
+
72
+ puts USD_to_JPY[:converted_amount] # => 0.13566633333e3
73
+ puts USD_to_JPY[:converted_amount].to_f # => 135.66633333
64
74
 
65
- puts USD_to_JPY # => 0.1345807e4
66
- puts USD_to_JPY.to_f # => 1345.807
75
+ puts USD_to_JPY[:exchange_rate] # => 0.13566633333e1
76
+ puts USD_to_JPY[:exchange_rate].to_f # => 1.3566633333
67
77
 
68
78
  ```
69
79
 
@@ -75,7 +85,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
75
85
 
76
86
  ## Contributing
77
87
 
78
- Bug reports and pull requests are welcome on GitHub at https://github.com/degica/omni_exchange. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/degica/omni_exchange/blob/master/CODE_OF_CONDUCT.md).
88
+ Bug reports and issues are welcome on GitHub at https://github.com/degica/omni_exchange. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/degica/omni_exchange/blob/master/CODE_OF_CONDUCT.md).
79
89
 
80
90
  ## License
81
91
 
data/bin/console CHANGED
@@ -5,8 +5,10 @@ require 'bundler/setup'
5
5
  require 'omni_exchange'
6
6
  require 'dotenv/load'
7
7
  require 'faraday'
8
+ require 'money'
8
9
  require_relative '../lib/omni_exchange/provider'
9
10
  require_relative '../lib/omni_exchange/configuration'
11
+ require_relative '../lib/omni_exchange/error'
10
12
  require_relative '../lib/omni_exchange/providers/xe'
11
13
  require_relative '../lib/omni_exchange/providers/open_exchange_rates'
12
14
 
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OmniExchange
4
+ # A custom error for an unknown or invalid currency
5
+ class UnknownCurrency < StandardError
6
+ end
7
+
8
+ # A custom error for failure to get data from a provider
9
+ class HttpError < StandardError
10
+ end
11
+
12
+ # A custom error for exceeding the monthly request limit for xe.com
13
+ class XeMonthlyLimit < StandardError
14
+ end
15
+ end
@@ -4,7 +4,6 @@ module OmniExchange
4
4
  class Provider
5
5
  # @providers is a hash of registered providers that OmniExchange can request exchange rates data from
6
6
  @providers = {}
7
- @currency_data = nil
8
7
 
9
8
  # This method registers providers by adding a provider's name as a key and a provider class as a value to
10
9
  # @providers. This happens automatically on load at the top of the lib/omni_exchange.rb file when each
@@ -43,18 +42,13 @@ module OmniExchange
43
42
 
44
43
  # Some currencies, such as the US dollar, have subunits (ie. cents). Therefore, to make sure that currencies are
45
44
  # exchanged accurately, a currency's subunit needs to be taken into account, and that's what this method does.
46
- # Subunit data of currencies is stored as @currency_data after being read from the currency_data.json file.
45
+ # Subunit data is easily found through use of the RubyMoney gem.
47
46
  #
48
47
  # @param base_currency [String] the ISO Currency Code of the currency that you're exchanging from. A check is done
49
48
  # on this currency to see if it has subunits (such as the US dollar having cents). ie. "USD", "JPY"
50
- # @return [Float] the amount an exchange rate should be multiplied by to account for subunits
49
+ # @return [Float] the amount an exchange rate should be multiplied by to account for a currency's potential subunits
51
50
  def self.get_currency_unit(base_currency)
52
- return 1.0 / @currency_data[base_currency.downcase]['subunit_to_unit'] if @currency_data
53
-
54
- file = File.read(File.join(File.dirname(__FILE__), './currency_data.json'))
55
- @currency_data = JSON.parse(file)
56
-
57
- 1.0 / @currency_data[base_currency.downcase]['subunit_to_unit']
51
+ 1.0 / Money::Currency.wrap(base_currency).subunit_to_unit
58
52
  end
59
53
  end
60
54
  end
@@ -33,9 +33,9 @@ module OmniExchange
33
33
 
34
34
  exchange_rate = JSON.parse(response.body, symbolize_names: true)[:rates][target_currency.to_sym].to_d
35
35
 
36
- currency_unit = get_currency_unit(base_currency)
36
+ currency_unit = get_currency_unit(base_currency).to_d
37
37
 
38
- exchange_rate * currency_unit
38
+ (exchange_rate * currency_unit).to_d
39
39
  end
40
40
 
41
41
  # when this file is required at the top of lib/omni_exchange.rb, this method call is run and allows
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # rubocop:disable Metrics/AbcSize
3
4
  require 'omni_exchange'
4
5
 
5
6
  module OmniExchange
@@ -36,7 +37,11 @@ module OmniExchange
36
37
  raise e.class, 'xe.com has timed out.'
37
38
  end
38
39
 
39
- JSON.parse(response.body, symbolize_names: true)[:to][0][:mid].to_d
40
+ body = JSON.parse(response.body, symbolize_names: true)
41
+
42
+ raise OmniExchange::XeMonthlyLimit, 'Xe.com monthly limit has been exceeded' if body[:code] == 3
43
+
44
+ body[:to][0][:mid].to_d
40
45
  end
41
46
 
42
47
  # when this file is required at the top of lib/omni_exchange.rb, this method call is run and allows
@@ -44,3 +49,4 @@ module OmniExchange
44
49
  OmniExchange::Provider.register_provider(:xe, self)
45
50
  end
46
51
  end
52
+ # rubocop:enable Metrics/AbcSize
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OmniExchange
4
- VERSION = '0.2.0'
4
+ VERSION = '1.2.0'
5
5
  end
data/lib/omni_exchange.rb CHANGED
@@ -7,7 +7,9 @@ require 'omni_exchange/providers/open_exchange_rates'
7
7
  require 'omni_exchange/providers/xe'
8
8
  require 'omni_exchange/version'
9
9
  require 'omni_exchange/configuration'
10
+ require 'omni_exchange/error'
10
11
  require 'faraday'
12
+ require 'money'
11
13
  require 'json'
12
14
  require 'bigdecimal/util'
13
15
 
@@ -32,8 +34,9 @@ module OmniExchange
32
34
 
33
35
  module_function
34
36
 
35
- # returns the amount of money in one country's currency when exchanged from an amount of money of another country's
36
- # currency using exchange rates data from API providers
37
+ # returns foreign exchange data including the amount of money in one country's currency when exchanged from an
38
+ # amount of money of another country's currency using exchange rates data from API providers, the exchange
39
+ # rate used to calculate that amount, and the API provider that supplied that rate.
37
40
  #
38
41
  # @param amount: [Integer, #to_d] the amount to exchange (in cents, if applicable to the currency). ie. 1, 10, 100
39
42
  # @param base_currency: [String] the ISO Currency Code of the currency that you're exchanging from. ie. "USD", "JPY"
@@ -41,41 +44,24 @@ module OmniExchange
41
44
  # @param providers: [Array] an array of symbols of the providers that will be used to get exchange rates API
42
45
  # data. The symbols must be found in the @providers hash in the Provider class (lib/omni_exchange/provider.rb).
43
46
  # ie. xe:, :open_exchange_rates
44
- # @return [BigDecimal] the amount of the base currency exchanged to the target currency using an exchange rate
47
+ # @return [Hash] If all of the providers in the providers hash fail to retrieve data, or if one of the currencies
48
+ # is not valid, an exception is raised.
49
+ # * :converted_amount [BigDecimal] the amount of money exchanged from the base currency to the target
50
+ # currency as a BigDecimal for precice calculation. ie. 1, 10, 100
51
+ # * :exchange_rate [BigDecimal] the rate used to calculate the converted_amount as a BigDecimal. ie. 0.95211e1
52
+ # * :provider_class [Class] the provider class that supplied the exchange_rate data. ie. OmniExchange::Xe
53
+ #the amount of the base currency exchanged to the target currency using an exchange rate
45
54
  # provided by one of the data providers in the providers hash. The final amount is returned as a BigDecimal
46
- # for precise calculation. If all of the providers in the providers hash fail to retrieve data,
47
- # an exception is raised.
48
- def exchange_currency(amount:, base_currency:, target_currency:, providers:)
49
- error_messages = []
50
-
51
- # Make sure all providers passed exist. If not, a LoadError is raise and not rescued
52
- provider_classes = providers.map { |p| OmniExchange::Provider.load_provider(p) }
53
-
54
- # Gracefully hit each provider and fail-over to the next one
55
- provider_classes.each do |klass|
56
- rate = klass.get_exchange_rate(base_currency: base_currency, target_currency: target_currency)
57
-
58
- return rate * amount.to_d
59
- rescue Faraday::Error, Faraday::ConnectionFailed => e
60
- error_messages << e.inspect
55
+ # for precise calculation. If all of the providers in the providers hash fail to retrieve data, an exception is raised.
56
+ def get_fx_data(amount:, base_currency:, target_currency:, providers:)
57
+ # if one of the currencies is not valid (ie. 'fake_crypto'), an exception is raised.
58
+ begin
59
+ Money::Currency.wrap(base_currency)
60
+ Money::Currency.wrap(target_currency)
61
+ rescue Money::Currency::UnknownCurrency => exception
62
+ raise OmniExchange::UnknownCurrency, "#{exception}"
61
63
  end
62
-
63
- raise "Failed to load #{base_currency}->#{target_currency}:\n#{exception_messages.join("\n")}"
64
- end
65
64
 
66
- # This method returns the exchange rate, the rate at which the smallest unit of one currency (the base currency)
67
- # will be exchanged for another currency (the target currency), from API providers
68
- #
69
- # @param base_currency: [String] the ISO Currency Code of the currency that you're exchanging from. ie. "USD", "JPY"
70
- # @param target_currency: [String] the ISO Currency Code of the currency that you're exchanging to. ie. "EUR", "KRW"
71
- # @param providers: [Array] an array of symbols of the providers that will be used to get exchange rates API
72
- # data. The symbols must be found in the @providers hash in the Provider class (lib/omni_exchange/provider.rb).
73
- # ie. xe:, :open_exchange_rates
74
- # @return [BigDecimal] the amount of the base currency exchanged to the target currency using an exchange rate
75
- # provided by one of the data providers in the providers hash. The final amount is returned as a BigDecimal
76
- # for precise calculation. If all of the providers in the providers hash fail to retrieve data,
77
- # an exception is raised.
78
- def get_exchange_rate(base_currency:, target_currency:, providers:)
79
65
  error_messages = []
80
66
 
81
67
  # Make sure all providers passed exist. If not, a LoadError is raise and not rescued
@@ -84,13 +70,15 @@ module OmniExchange
84
70
  # Gracefully hit each provider and fail-over to the next one
85
71
  provider_classes.each do |klass|
86
72
  rate = klass.get_exchange_rate(base_currency: base_currency, target_currency: target_currency)
87
-
88
- return rate
89
- rescue Faraday::Error, Faraday::ConnectionFailed => e
73
+
74
+ exchanged_amount = rate.to_d * amount.to_d
75
+
76
+ return { converted_amount: exchanged_amount, exchange_rate: rate, provider_class: klass }
77
+ rescue Faraday::Error, Faraday::ConnectionFailed, OmniExchange::XeMonthlyLimit => e
90
78
  error_messages << e.inspect
91
79
  end
92
80
 
93
- raise "Failed to load #{base_currency}->#{target_currency}:\n#{exception_messages.join("\n")}"
81
+ raise OmniExchange::HttpError, "Failed to load #{base_currency}->#{target_currency}:\n#{error_messages.join("\n")}"
94
82
  end
95
83
  end
96
84
  # rubocop:enable Lint/Syntax
@@ -28,6 +28,7 @@ Gem::Specification.new do |spec|
28
28
  spec.require_paths = ['lib']
29
29
 
30
30
  spec.add_dependency 'faraday', '0.17.4'
31
+ spec.add_dependency 'money', '~> 6.13.1'
31
32
 
32
33
  spec.add_development_dependency 'dotenv'
33
34
  spec.add_development_dependency 'pry'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: omni_exchange
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yun Chung
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-06-27 00:00:00.000000000 Z
11
+ date: 2022-07-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - '='
25
25
  - !ruby/object:Gem::Version
26
26
  version: 0.17.4
27
+ - !ruby/object:Gem::Dependency
28
+ name: money
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 6.13.1
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 6.13.1
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: dotenv
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -132,7 +146,7 @@ files:
132
146
  - bin/setup
133
147
  - lib/omni_exchange.rb
134
148
  - lib/omni_exchange/configuration.rb
135
- - lib/omni_exchange/currency_data.json
149
+ - lib/omni_exchange/error.rb
136
150
  - lib/omni_exchange/provider.rb
137
151
  - lib/omni_exchange/providers/open_exchange_rates.rb
138
152
  - lib/omni_exchange/providers/xe.rb