money-open-exchange-rates 1.4.2 → 2.0.1
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/History.md +16 -0
- data/README.md +6 -0
- data/lib/money/bank/open_exchange_rates_bank.rb +21 -12
- data/lib/open_exchange_rates_bank/version.rb +1 -1
- data/test/data/partial_latest.json +16 -0
- data/test/integration/Gemfile +1 -1
- data/test/integration/api.rb +5 -4
- data/test/open_exchange_rates_bank_test.rb +47 -4
- data/test/test_helper.rb +4 -0
- metadata +30 -24
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3b5f63af5ef2482e223249c50e9972e84c4f9aaa3bcec4b3052089ee3b60a237
|
|
4
|
+
data.tar.gz: aa6a135ef6dea76c5ca26739dcdabfe5ba2eac6bf83eba3c8b060f54c5357dc6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a78deced5445998e4fd6dd9cfa442753dca96bdbeabae0fbf0611e8c1274542030de8ed5be8f7b20a670693095370cb4a72e9a205ac556c1ce075f987ed128e5
|
|
7
|
+
data.tar.gz: b865cbfd1b40ab43f186aa99e1af7b01314eb90a46cecd586634b2b9e4a83614c99f555bf2aa3c7dd8419aa4faea85429026814f6c5299e657fba015c9d728a8
|
data/History.md
CHANGED
|
@@ -1,4 +1,20 @@
|
|
|
1
1
|
|
|
2
|
+
2.0.1 / 2026-01-21
|
|
3
|
+
==================
|
|
4
|
+
|
|
5
|
+
* Merge pull request #75 from @ps-97 / adarsh/preserve-rates-on-partial-api-response
|
|
6
|
+
* fix: preserve rates for currencies missing from API response
|
|
7
|
+
|
|
8
|
+
2.0.0 / 2025-12-23
|
|
9
|
+
==================
|
|
10
|
+
|
|
11
|
+
* BREAKING: Upgrade to money ~> 7.0
|
|
12
|
+
* BREAKING: Require Ruby >= 3.1
|
|
13
|
+
* Update monetize dependency to ~> 2.0
|
|
14
|
+
* Update CI to test Ruby 3.1, 3.2, 3.3, 3.4
|
|
15
|
+
* Remove support for Ruby 2.7, 3.0
|
|
16
|
+
* Update README with money 7.x default currency configuration
|
|
17
|
+
|
|
2
18
|
1.4.2 / 2023-07-25
|
|
3
19
|
==================
|
|
4
20
|
|
data/README.md
CHANGED
|
@@ -45,6 +45,9 @@ gem install money-open-exchange-rates
|
|
|
45
45
|
``` ruby
|
|
46
46
|
require 'money/bank/open_exchange_rates_bank'
|
|
47
47
|
|
|
48
|
+
# Money 7.x requires explicit default currency configuration
|
|
49
|
+
Money.default_currency = Money::Currency.new('USD')
|
|
50
|
+
|
|
48
51
|
# Memory store per default; for others just pass as argument a class like
|
|
49
52
|
# explained in https://github.com/RubyMoney/money#exchange-rate-stores
|
|
50
53
|
oxr = Money::Bank::OpenExchangeRatesBank.new(Money::RatesStore::Memory.new)
|
|
@@ -176,6 +179,9 @@ take some time (HTTP call) and can fail.
|
|
|
176
179
|
``` ruby
|
|
177
180
|
require 'money/bank/open_exchange_rates_bank'
|
|
178
181
|
|
|
182
|
+
# Money 7.x requires explicit default currency configuration
|
|
183
|
+
Money.default_currency = Money::Currency.new('USD')
|
|
184
|
+
|
|
179
185
|
OXR_CACHE_KEY = "#{Rails.env}:money:exchange_rates".freeze
|
|
180
186
|
# ExchangeRate is an ActiveRecord model
|
|
181
187
|
# more info at https://github.com/RubyMoney/money#exchange-rate-stores
|
|
@@ -187,15 +187,14 @@ class Money
|
|
|
187
187
|
# @return [Array] Array of exchange rates
|
|
188
188
|
def update_rates
|
|
189
189
|
store.transaction do
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
rate = exchange_rate.last
|
|
193
|
-
currency = exchange_rate.first
|
|
190
|
+
new_rates = exchange_rates
|
|
191
|
+
new_rates.each do |currency, rate|
|
|
194
192
|
next unless Money::Currency.find(currency)
|
|
195
193
|
|
|
196
194
|
set_rate(source, currency, rate)
|
|
197
195
|
set_rate(currency, source, 1.0 / rate)
|
|
198
196
|
end
|
|
197
|
+
clear_cross_rates_for(new_rates)
|
|
199
198
|
end
|
|
200
199
|
end
|
|
201
200
|
|
|
@@ -270,7 +269,7 @@ class Money
|
|
|
270
269
|
str = "#{str}&base=#{source}" unless source == OE_SOURCE
|
|
271
270
|
str = "#{str}&show_alternative=#{show_alternative}"
|
|
272
271
|
str = "#{str}&prettyprint=#{prettyprint}"
|
|
273
|
-
str = "#{str}&symbols=#{symbols.join(',')}" if symbols
|
|
272
|
+
str = "#{str}&symbols=#{symbols.join(',')}" if symbols.is_a?(Array)
|
|
274
273
|
str
|
|
275
274
|
end
|
|
276
275
|
|
|
@@ -324,9 +323,7 @@ class Money
|
|
|
324
323
|
if cache.is_a?(Proc)
|
|
325
324
|
cache.call(text)
|
|
326
325
|
elsif cache.is_a?(String) || cache.is_a?(Pathname)
|
|
327
|
-
File.
|
|
328
|
-
f.write(text)
|
|
329
|
-
end
|
|
326
|
+
File.write(cache.to_s, text)
|
|
330
327
|
else
|
|
331
328
|
raise InvalidCache
|
|
332
329
|
end
|
|
@@ -373,7 +370,7 @@ class Money
|
|
|
373
370
|
return false unless text
|
|
374
371
|
|
|
375
372
|
parsed = JSON.parse(text)
|
|
376
|
-
parsed&.key?(RATES_KEY) && parsed
|
|
373
|
+
parsed&.key?(RATES_KEY) && parsed.key?(TIMESTAMP_KEY)
|
|
377
374
|
rescue JSON::ParserError
|
|
378
375
|
false
|
|
379
376
|
end
|
|
@@ -383,9 +380,7 @@ class Money
|
|
|
383
380
|
# @return [Hash] key is country code (ISO 3166-1 alpha-3) value Float
|
|
384
381
|
def exchange_rates
|
|
385
382
|
doc = JSON.parse(read_from_cache || read_from_url)
|
|
386
|
-
if doc['error'] && ERROR_MAP.key?(doc['message'].to_sym)
|
|
387
|
-
raise ERROR_MAP[doc['message'].to_sym]
|
|
388
|
-
end
|
|
383
|
+
raise ERROR_MAP[doc['message'].to_sym] if doc['error'] && ERROR_MAP.key?(doc['message'].to_sym)
|
|
389
384
|
|
|
390
385
|
self.rates_timestamp = doc[TIMESTAMP_KEY]
|
|
391
386
|
@oer_rates = doc[RATES_KEY]
|
|
@@ -442,6 +437,20 @@ class Money
|
|
|
442
437
|
add_rate(iso_from, iso_to, nil)
|
|
443
438
|
end
|
|
444
439
|
end
|
|
440
|
+
|
|
441
|
+
# Clears cross-rates for currencies present in new_rates
|
|
442
|
+
# Cross-rates are calculated on next access via calc_pair_rate_using_base
|
|
443
|
+
# Direct rates (source -> X) are not cleared, just overwritten
|
|
444
|
+
#
|
|
445
|
+
# @param new_rates [Hash] Rates hash from API response
|
|
446
|
+
def clear_cross_rates_for(new_rates)
|
|
447
|
+
store.each_rate do |iso_from, iso_to|
|
|
448
|
+
next if iso_from == source || iso_to == source
|
|
449
|
+
next unless new_rates.key?(iso_from) || new_rates.key?(iso_to)
|
|
450
|
+
|
|
451
|
+
add_rate(iso_from, iso_to, nil)
|
|
452
|
+
end
|
|
453
|
+
end
|
|
445
454
|
end
|
|
446
455
|
end
|
|
447
456
|
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"disclaimer": "Exchange rates are provided for informational purposes only.",
|
|
3
|
+
"license": "Data sourced from various providers.",
|
|
4
|
+
"timestamp": 1414008100,
|
|
5
|
+
"base": "USD",
|
|
6
|
+
"rates": {
|
|
7
|
+
"AED": 3.67304,
|
|
8
|
+
"AUD": 1.139103,
|
|
9
|
+
"BBD": 2,
|
|
10
|
+
"CAD": 1.124161,
|
|
11
|
+
"CHF": 0.951922,
|
|
12
|
+
"GBP": 0.62292,
|
|
13
|
+
"JPY": 107.0718,
|
|
14
|
+
"USD": 1
|
|
15
|
+
}
|
|
16
|
+
}
|
data/test/integration/Gemfile
CHANGED
data/test/integration/api.rb
CHANGED
|
@@ -3,10 +3,13 @@
|
|
|
3
3
|
require 'json'
|
|
4
4
|
require 'money/bank/open_exchange_rates_bank'
|
|
5
5
|
|
|
6
|
+
# Money 7.x requires explicit default currency configuration
|
|
7
|
+
Money.default_currency = Money::Currency.new('USD')
|
|
8
|
+
|
|
6
9
|
ERROR_MSG = 'Integration test failed!'
|
|
7
10
|
cache_path = '/tmp/latest.json'
|
|
8
11
|
to_currency = 'CAD'
|
|
9
|
-
app_id = ENV
|
|
12
|
+
app_id = ENV.fetch('OXR_APP_ID', nil)
|
|
10
13
|
|
|
11
14
|
if app_id.nil? || app_id.empty?
|
|
12
15
|
puts 'OXR_APP_ID env var not set skipping integration tests'
|
|
@@ -29,10 +32,8 @@ begin
|
|
|
29
32
|
json_to_currency = JSON.parse(File.read(cache_path))['rates'][to_currency]
|
|
30
33
|
puts 'JSON to_currency', json_to_currency
|
|
31
34
|
puts 'Money to_currency', cad_rate
|
|
32
|
-
# rubocop:disable Style/AndOr
|
|
33
35
|
json_to_currency == cad_rate or raise ERROR_MSG
|
|
34
|
-
|
|
35
|
-
# rubocop:disable Style/RescueStandardError
|
|
36
|
+
# rubocop:disable Style/RescueStandardError
|
|
36
37
|
rescue
|
|
37
38
|
# rubocop:enable Style/RescueStandardError
|
|
38
39
|
raise ERROR_MSG
|
|
@@ -117,7 +117,7 @@ describe Money::Bank::OpenExchangeRatesBank do
|
|
|
117
117
|
end
|
|
118
118
|
|
|
119
119
|
it 'should update itself with exchange rates from OpenExchangeRates' do
|
|
120
|
-
subject.oer_rates.
|
|
120
|
+
subject.oer_rates.each_key do |currency|
|
|
121
121
|
next unless Money::Currency.find(currency)
|
|
122
122
|
|
|
123
123
|
_(subject.get_rate('USD', currency)).must_be :>, 0
|
|
@@ -316,21 +316,21 @@ describe Money::Bank::OpenExchangeRatesBank do
|
|
|
316
316
|
initial_size = File.read(temp_cache_path).size
|
|
317
317
|
subject.stubs(:api_response).returns ''
|
|
318
318
|
subject.refresh_rates
|
|
319
|
-
_(File.
|
|
319
|
+
_(File.read(temp_cache_path).size).must_equal initial_size
|
|
320
320
|
end
|
|
321
321
|
|
|
322
322
|
it 'should not break an existing file if save returns json without rates' do
|
|
323
323
|
initial_size = File.read(temp_cache_path).size
|
|
324
324
|
subject.stubs(:api_response).returns '{"error": "An error"}'
|
|
325
325
|
subject.refresh_rates
|
|
326
|
-
_(File.
|
|
326
|
+
_(File.read(temp_cache_path).size).must_equal initial_size
|
|
327
327
|
end
|
|
328
328
|
|
|
329
329
|
it 'should not break an existing file if save returns a invalid json' do
|
|
330
330
|
initial_size = File.read(temp_cache_path).size
|
|
331
331
|
subject.stubs(:api_response).returns '{invalid_json: "An error"}'
|
|
332
332
|
subject.refresh_rates
|
|
333
|
-
_(File.
|
|
333
|
+
_(File.read(temp_cache_path).size).must_equal initial_size
|
|
334
334
|
end
|
|
335
335
|
end
|
|
336
336
|
|
|
@@ -533,5 +533,48 @@ describe Money::Bank::OpenExchangeRatesBank do
|
|
|
533
533
|
end
|
|
534
534
|
end
|
|
535
535
|
end
|
|
536
|
+
|
|
537
|
+
describe 'missing currencies' do
|
|
538
|
+
let(:oer_partial_path) do
|
|
539
|
+
data_file('partial_latest.json')
|
|
540
|
+
end
|
|
541
|
+
|
|
542
|
+
before do
|
|
543
|
+
add_to_webmock(subject)
|
|
544
|
+
# see test/data/latest.json +52
|
|
545
|
+
@latest_usd_eur_rate = 0.79085
|
|
546
|
+
# see test/data/latest.json +33
|
|
547
|
+
@latest_usd_cad_rate = 1.124161
|
|
548
|
+
subject.update_rates
|
|
549
|
+
end
|
|
550
|
+
|
|
551
|
+
it 'should preserve direct rates' do
|
|
552
|
+
_(subject.get_rate('USD', 'EUR')).must_equal @latest_usd_eur_rate
|
|
553
|
+
subject.cache = nil
|
|
554
|
+
stub_request(:get, subject.source_url)
|
|
555
|
+
.to_return(status: 200, body: File.read(oer_partial_path))
|
|
556
|
+
subject.update_rates
|
|
557
|
+
_(subject.get_rate('USD', 'EUR')).must_equal @latest_usd_eur_rate
|
|
558
|
+
end
|
|
559
|
+
|
|
560
|
+
it 'should update rates for currencies in response' do
|
|
561
|
+
_(subject.get_rate('USD', 'CAD')).must_equal @latest_usd_cad_rate
|
|
562
|
+
subject.cache = nil
|
|
563
|
+
stub_request(:get, subject.source_url)
|
|
564
|
+
.to_return(status: 200, body: File.read(oer_partial_path))
|
|
565
|
+
subject.update_rates
|
|
566
|
+
_(subject.get_rate('USD', 'CAD')).must_equal @latest_usd_cad_rate
|
|
567
|
+
end
|
|
568
|
+
|
|
569
|
+
it 'should exchange with preserved rates' do
|
|
570
|
+
money = Money.new(100, 'USD')
|
|
571
|
+
_(subject.exchange_with(money, 'EUR')).must_equal Money.new(79, 'EUR')
|
|
572
|
+
subject.cache = nil
|
|
573
|
+
stub_request(:get, subject.source_url)
|
|
574
|
+
.to_return(status: 200, body: File.read(oer_partial_path))
|
|
575
|
+
subject.update_rates
|
|
576
|
+
_(subject.exchange_with(money, 'EUR')).must_equal Money.new(79, 'EUR')
|
|
577
|
+
end
|
|
578
|
+
end
|
|
536
579
|
end
|
|
537
580
|
# rubocop:enable Metrics/BlockLength
|
data/test/test_helper.rb
CHANGED
|
@@ -7,6 +7,7 @@ rescue LoadError
|
|
|
7
7
|
warn 'simplecov not loaded'
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
+
require 'minitest/unit'
|
|
10
11
|
require 'minitest/autorun'
|
|
11
12
|
require 'mocha/minitest'
|
|
12
13
|
require 'webmock/minitest'
|
|
@@ -14,6 +15,9 @@ require 'money/bank/open_exchange_rates_bank'
|
|
|
14
15
|
require 'monetize'
|
|
15
16
|
require 'timecop'
|
|
16
17
|
|
|
18
|
+
# Money 7.x requires explicit default currency configuration
|
|
19
|
+
Money.default_currency = Money::Currency.new('USD')
|
|
20
|
+
|
|
17
21
|
TEST_APP_ID = 'TEST_APP_ID'
|
|
18
22
|
|
|
19
23
|
def data_file(file)
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: money-open-exchange-rates
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 2.0.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Laurent Arnoud
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: money
|
|
@@ -16,14 +15,28 @@ dependencies:
|
|
|
16
15
|
requirements:
|
|
17
16
|
- - "~>"
|
|
18
17
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '
|
|
18
|
+
version: '7.0'
|
|
20
19
|
type: :runtime
|
|
21
20
|
prerelease: false
|
|
22
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
22
|
requirements:
|
|
24
23
|
- - "~>"
|
|
25
24
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: '
|
|
25
|
+
version: '7.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: ostruct
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0.6'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0.6'
|
|
27
40
|
- !ruby/object:Gem::Dependency
|
|
28
41
|
name: minitest
|
|
29
42
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -56,22 +69,16 @@ dependencies:
|
|
|
56
69
|
name: monetize
|
|
57
70
|
requirement: !ruby/object:Gem::Requirement
|
|
58
71
|
requirements:
|
|
59
|
-
- - "
|
|
60
|
-
- !ruby/object:Gem::Version
|
|
61
|
-
version: 1.3.1
|
|
62
|
-
- - "<"
|
|
72
|
+
- - "~>"
|
|
63
73
|
- !ruby/object:Gem::Version
|
|
64
|
-
version: '2'
|
|
74
|
+
version: '2.0'
|
|
65
75
|
type: :development
|
|
66
76
|
prerelease: false
|
|
67
77
|
version_requirements: !ruby/object:Gem::Requirement
|
|
68
78
|
requirements:
|
|
69
|
-
- - "
|
|
70
|
-
- !ruby/object:Gem::Version
|
|
71
|
-
version: 1.3.1
|
|
72
|
-
- - "<"
|
|
79
|
+
- - "~>"
|
|
73
80
|
- !ruby/object:Gem::Version
|
|
74
|
-
version: '2'
|
|
81
|
+
version: '2.0'
|
|
75
82
|
- !ruby/object:Gem::Dependency
|
|
76
83
|
name: rake
|
|
77
84
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -92,14 +99,14 @@ dependencies:
|
|
|
92
99
|
requirements:
|
|
93
100
|
- - "~>"
|
|
94
101
|
- !ruby/object:Gem::Version
|
|
95
|
-
version:
|
|
102
|
+
version: '1.69'
|
|
96
103
|
type: :development
|
|
97
104
|
prerelease: false
|
|
98
105
|
version_requirements: !ruby/object:Gem::Requirement
|
|
99
106
|
requirements:
|
|
100
107
|
- - "~>"
|
|
101
108
|
- !ruby/object:Gem::Version
|
|
102
|
-
version:
|
|
109
|
+
version: '1.69'
|
|
103
110
|
- !ruby/object:Gem::Dependency
|
|
104
111
|
name: timecop
|
|
105
112
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -146,6 +153,7 @@ files:
|
|
|
146
153
|
- test/data/access_restricted_error.json
|
|
147
154
|
- test/data/app_id_inactive.json
|
|
148
155
|
- test/data/latest.json
|
|
156
|
+
- test/data/partial_latest.json
|
|
149
157
|
- test/integration/Gemfile
|
|
150
158
|
- test/integration/Gemfile.lock
|
|
151
159
|
- test/integration/api.rb
|
|
@@ -154,8 +162,8 @@ files:
|
|
|
154
162
|
homepage: http://github.com/spk/money-open-exchange-rates
|
|
155
163
|
licenses:
|
|
156
164
|
- MIT
|
|
157
|
-
metadata:
|
|
158
|
-
|
|
165
|
+
metadata:
|
|
166
|
+
rubygems_mfa_required: 'true'
|
|
159
167
|
rdoc_options: []
|
|
160
168
|
require_paths:
|
|
161
169
|
- lib
|
|
@@ -163,16 +171,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
163
171
|
requirements:
|
|
164
172
|
- - ">="
|
|
165
173
|
- !ruby/object:Gem::Version
|
|
166
|
-
version: '
|
|
174
|
+
version: '3.1'
|
|
167
175
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
168
176
|
requirements:
|
|
169
177
|
- - ">="
|
|
170
178
|
- !ruby/object:Gem::Version
|
|
171
179
|
version: '0'
|
|
172
180
|
requirements: []
|
|
173
|
-
rubygems_version: 3.
|
|
174
|
-
signing_key:
|
|
181
|
+
rubygems_version: 3.6.7
|
|
175
182
|
specification_version: 4
|
|
176
183
|
summary: A gem that calculates the exchange rate using published rates from open-exchange-rates.
|
|
177
|
-
test_files:
|
|
178
|
-
- test/open_exchange_rates_bank_test.rb
|
|
184
|
+
test_files: []
|