fx_exchange_rate 1.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.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/.rdoc_options +32 -0
- data/.rspec +3 -0
- data/.rubocop.yml +17 -0
- data/.ruby-version +1 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +100 -0
- data/LICENSE.txt +21 -0
- data/README.md +106 -0
- data/Rakefile +8 -0
- data/bin/console +12 -0
- data/bin/setup +8 -0
- data/fixtures/vcr_cassettes/ExchangeRate/_retrieve/fetches_the_feed_and_stores_the_values_locally.yml +112 -0
- data/fixtures/vcr_cassettes/ExchangeRate/_retrieve/overwrites_existing_values.yml +112 -0
- data/fixtures/vcr_cassettes/ExchangeRate/_retrieve/raises_ExchangeRate_RetrievalFailed_when_the_the_feed_cannot_be_retrieved.yml +66 -0
- data/fixtures/vcr_cassettes/ExchangeRate_RateSources_ECBRateRetriever/_save_/raises_when_the_feed_content_is_invalid.yml +64 -0
- data/fixtures/vcr_cassettes/ExchangeRate_RateSources_ECBRateRetriever/_save_/retrieves_the_feed.yml +112 -0
- data/fx_exchange_rate.gemspec +39 -0
- data/lib/exchange_rate.rb +31 -0
- data/lib/exchange_rate/configuration.rb +35 -0
- data/lib/exchange_rate/currency_converter.rb +78 -0
- data/lib/exchange_rate/currency_rate.rb +34 -0
- data/lib/exchange_rate/database_connection.rb +37 -0
- data/lib/exchange_rate/db/migrate/20180827184400_create_currency_rates.rb +18 -0
- data/lib/exchange_rate/initializers/sequel.rb +12 -0
- data/lib/exchange_rate/missing_rate_error.rb +8 -0
- data/lib/exchange_rate/module_methods.rb +87 -0
- data/lib/exchange_rate/rate_sources.rb +9 -0
- data/lib/exchange_rate/rate_sources/e_c_b_rate_retriever.rb +119 -0
- data/lib/exchange_rate/retrieval_failed_error.rb +7 -0
- data/lib/exchange_rate/version.rb +7 -0
- metadata +247 -0
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ExchangeRate
|
4
|
+
##
|
5
|
+
# Convert between currencies, using the local cache store
|
6
|
+
#
|
7
|
+
# Examples
|
8
|
+
#
|
9
|
+
# CurrencyConverter.new(Date.today, 'GBP', 'USD').convert!
|
10
|
+
# # => 1.291700027
|
11
|
+
#
|
12
|
+
class CurrencyConverter
|
13
|
+
##
|
14
|
+
# Builds a CurrencyConverter. The cache is not accessed on initialization.
|
15
|
+
#
|
16
|
+
# date_of_rate - The date the rate should be used, for historical conversions.
|
17
|
+
# source_currency - The source currency code, e.g. 'GBP'.
|
18
|
+
# target_currency - The target currency code, e.g. 'USD'.
|
19
|
+
def initialize(date_of_rate, source_currency, target_currency)
|
20
|
+
@date_of_rate = date_of_rate
|
21
|
+
@source_currency = source_currency
|
22
|
+
@target_currency = target_currency
|
23
|
+
end
|
24
|
+
|
25
|
+
##
|
26
|
+
# Convert between the source and target currencies.
|
27
|
+
# By default this will take 1 unit of the source currency.
|
28
|
+
#
|
29
|
+
# source_value - The number of units of source currency to convert.
|
30
|
+
#
|
31
|
+
# Examples
|
32
|
+
#
|
33
|
+
# converter = CurrencyConverter.new(Date.today, 'GBP', 'USD')
|
34
|
+
#
|
35
|
+
# converter.convert
|
36
|
+
# # => 1.291700027
|
37
|
+
#
|
38
|
+
# converter.convert(source_value: 2)
|
39
|
+
# # => 2.583400054
|
40
|
+
#
|
41
|
+
def convert!(source_value: 1)
|
42
|
+
(source_value / source_rate) * target_rate
|
43
|
+
end
|
44
|
+
|
45
|
+
##
|
46
|
+
# The (cached) source ExchangeRate::CurrencyRate object.
|
47
|
+
#
|
48
|
+
# Returns ExchangeRate::CurrencyRate.
|
49
|
+
#
|
50
|
+
# Raises ExchangeRate::MissingRateError if a cached rate does not exist.
|
51
|
+
def source_rate
|
52
|
+
find_currency_rate(@source_currency, @date_of_rate).value_in_euro
|
53
|
+
end
|
54
|
+
|
55
|
+
##
|
56
|
+
# The (cached) source ExchangeRate::CurrencyRate object.
|
57
|
+
#
|
58
|
+
# Returns ExchangeRate::CurrencyRate.
|
59
|
+
#
|
60
|
+
# Raises ExchangeRate::MissingRateError if a cached rate does not exist.
|
61
|
+
def target_rate
|
62
|
+
find_currency_rate(@target_currency, @date_of_rate).value_in_euro
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
##
|
68
|
+
# Finds a currency rate
|
69
|
+
#
|
70
|
+
# Returns ExchangeRate::CurrencyRate.
|
71
|
+
#
|
72
|
+
# Raises ExchangeRate::MissingRateError if a cached rate does not exist.
|
73
|
+
def find_currency_rate(currency, date_of_rate)
|
74
|
+
CurrencyRate.first(currency: currency, date_of_rate: date_of_rate)
|
75
|
+
.tap { |rate| raise ExchangeRate::MissingRateError if rate.nil? }
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'sequel'
|
4
|
+
require 'exchange_rate/database_connection'
|
5
|
+
|
6
|
+
module ExchangeRate
|
7
|
+
##
|
8
|
+
# A currency/rate/date tuple. Represents the
|
9
|
+
# value of the currency on a specific date, in Euro
|
10
|
+
# Examples
|
11
|
+
#
|
12
|
+
# CurrencyRate.new(currency = 'GBP',
|
13
|
+
# value_in_euro = 0.001,
|
14
|
+
# date_of_rate = Date.parse('2019-03-30'))
|
15
|
+
#
|
16
|
+
class CurrencyRate < Sequel::Model(DatabaseConnection.connection[:currency_rates])
|
17
|
+
##
|
18
|
+
# :attr_writer: currency
|
19
|
+
# The currency short-code String
|
20
|
+
|
21
|
+
##
|
22
|
+
# :attr_writer: value_in_euro
|
23
|
+
# The value of one unit of the currency in Euro
|
24
|
+
|
25
|
+
##
|
26
|
+
# :attr_writer: date_of_rate
|
27
|
+
# The effective date of this FX rate for the currency
|
28
|
+
|
29
|
+
def validate
|
30
|
+
super
|
31
|
+
validates_presence %i[currency value_in_euro date_of_rate]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'sequel'
|
4
|
+
require 'sequel/extensions/migration'
|
5
|
+
require 'yaml'
|
6
|
+
|
7
|
+
module ExchangeRate
|
8
|
+
##
|
9
|
+
# Database connection management for ::ExchangeRate.
|
10
|
+
class DatabaseConnection
|
11
|
+
##
|
12
|
+
# Establish a connection to the database, and ensure the schema is
|
13
|
+
# loaded. The connection is cached and kept open.
|
14
|
+
#
|
15
|
+
# Returns nothing
|
16
|
+
def self.connection
|
17
|
+
database_url = ExchangeRate.configuration.datastore_url
|
18
|
+
@connection ||= Sequel.connect(database_url).tap { |connection| apply_migrations(connection) }
|
19
|
+
end
|
20
|
+
|
21
|
+
##
|
22
|
+
# Closes any open connections to the database and removes the cached connection.
|
23
|
+
def self.disconnect
|
24
|
+
@connection&.disconnect
|
25
|
+
@connection = nil
|
26
|
+
end
|
27
|
+
|
28
|
+
##
|
29
|
+
# Apply the schema definitions to the database.
|
30
|
+
#
|
31
|
+
# Returns nothing
|
32
|
+
def self.apply_migrations(connection)
|
33
|
+
return if connection.nil?
|
34
|
+
Sequel::Migrator.run(connection, 'lib/exchange_rate/db/migrate/')
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class CreateCurrencyRates < Sequel::Migration
|
4
|
+
def up
|
5
|
+
create_table(:currency_rates) do |_t|
|
6
|
+
primary_key :id
|
7
|
+
String :currency, null: false
|
8
|
+
BigDecimal :value_in_euro, null: false
|
9
|
+
Date :date_of_rate, null: false
|
10
|
+
|
11
|
+
index %i[currency date_of_rate], unique: true
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def down
|
16
|
+
self << 'DROP TABLE currency_rates'
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'sequel'
|
4
|
+
|
5
|
+
# ActiveRecord-style migrations
|
6
|
+
Sequel.extension :migration
|
7
|
+
|
8
|
+
# Add helpers for when we're not sure if a record exists or not
|
9
|
+
Sequel::Model.plugin :update_or_create
|
10
|
+
|
11
|
+
# Adds ActiveRecord-style #validates_presence helpers
|
12
|
+
Sequel::Model.plugin :validation_helpers
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
##
|
4
|
+
# Module Methods for ExchangeRate.
|
5
|
+
#
|
6
|
+
#--
|
7
|
+
# These methods are here because the configuration methods are needed in other classes which are
|
8
|
+
# required above the ExchangeRate module definition. It felt cleaner to move the module methods
|
9
|
+
# here than shift the module definition above the require statements.
|
10
|
+
#
|
11
|
+
# Note: this defines methods on the module rather than providing a mixin because the latter still
|
12
|
+
# required the mixin to be included in the module of any class which referenced it. That might be
|
13
|
+
# cleaner in the long run.
|
14
|
+
#++
|
15
|
+
module ExchangeRate
|
16
|
+
##
|
17
|
+
# Retrieve the remote FX rate feed and cache locally.
|
18
|
+
#
|
19
|
+
# Returns nothing.
|
20
|
+
#
|
21
|
+
# Raises ExchangeRate::RetrievalFailedError if the retrieval fails or the cache cannot be updated.
|
22
|
+
def self.retrieve
|
23
|
+
configuration.rate_retriever.save!
|
24
|
+
nil
|
25
|
+
# We expect all rate retrievers to be nice and return ExchangeRate::RetrievalFailed
|
26
|
+
# but we'll catch everything here to safeguard against custom providers' errors
|
27
|
+
# bubbling up to the caller
|
28
|
+
rescue StandardError
|
29
|
+
raise ExchangeRate::RetrievalFailedError
|
30
|
+
end
|
31
|
+
|
32
|
+
##
|
33
|
+
# Converts from from_currency to to_currency using the FX rate
|
34
|
+
# valid on the date_of_rate.
|
35
|
+
#
|
36
|
+
# date_of_rate - The Date to use for conversion
|
37
|
+
# from_currency - The source currency code String
|
38
|
+
# from_currency - The target currency code String
|
39
|
+
#
|
40
|
+
# Examples
|
41
|
+
#
|
42
|
+
# ExchangeRate.at(Date.today,'GBP','USD')
|
43
|
+
# # => 0.12883019198912e1
|
44
|
+
#
|
45
|
+
# Returns the value of 1 unit of from_currency in to_currency.
|
46
|
+
#
|
47
|
+
# Raises ExchangeRate::MissingRateError if the local cache cannot be accessed or the value has not been cached.
|
48
|
+
def self.at(date_of_rate, from_currency, to_currency)
|
49
|
+
ExchangeRate::CurrencyConverter.new(date_of_rate, from_currency, to_currency).convert!
|
50
|
+
rescue StandardError
|
51
|
+
raise ExchangeRate::MissingRateError
|
52
|
+
end
|
53
|
+
|
54
|
+
##
|
55
|
+
# The configuration object used by this module.
|
56
|
+
#
|
57
|
+
# See ExchangeRate::Configuration.
|
58
|
+
#
|
59
|
+
# Returns a singleton ExchangeRate::Configuration global configuration object.
|
60
|
+
def self.configuration
|
61
|
+
@configuration ||= Configuration.new
|
62
|
+
end
|
63
|
+
|
64
|
+
##
|
65
|
+
# Set the configuration object used by this module.
|
66
|
+
#
|
67
|
+
# See ExchangeRate::Configuration.
|
68
|
+
def self.configuration=(configuration)
|
69
|
+
@configuration = configuration
|
70
|
+
end
|
71
|
+
|
72
|
+
##
|
73
|
+
# Configures the ExchangeRate module with the provided block.
|
74
|
+
#
|
75
|
+
# See ExchangeRate::Configuration.
|
76
|
+
#
|
77
|
+
# Examples
|
78
|
+
#
|
79
|
+
# ExchangeRate.configure do |configuration|
|
80
|
+
# configuration.rate_retriever = CustomRateRetriever.new
|
81
|
+
# end
|
82
|
+
#
|
83
|
+
# Returns nothing
|
84
|
+
def self.configure
|
85
|
+
yield(configuration)
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/core_ext/hash/conversions.rb'
|
4
|
+
require 'net/http'
|
5
|
+
require 'sequel/exceptions'
|
6
|
+
require 'uri'
|
7
|
+
|
8
|
+
module ExchangeRate
|
9
|
+
module RateSources
|
10
|
+
##
|
11
|
+
# Retrieves FX rates from the 90 day European Central Bank (ECB) feed.
|
12
|
+
class ECBRateRetriever
|
13
|
+
##
|
14
|
+
# The ECB feed URL
|
15
|
+
DEFAULT_FEED_URL = 'http://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist-90d.xml'
|
16
|
+
|
17
|
+
##
|
18
|
+
# Builds a new ECBRateRetriever.
|
19
|
+
#
|
20
|
+
# - feed_url - The URL to request the ECB feed from.
|
21
|
+
def initialize(feed_url: DEFAULT_FEED_URL)
|
22
|
+
@feed_url = feed_url
|
23
|
+
end
|
24
|
+
|
25
|
+
##
|
26
|
+
# Retrieve the feed, parse the contents, and update the local cache.
|
27
|
+
#
|
28
|
+
# Returns nothing
|
29
|
+
# Raises ExchangeRate::RetrievalFailedError if the feed could not be loaded
|
30
|
+
# Raises ExchangeRate::RetrievalFailedError if the cache could not be updated
|
31
|
+
#--
|
32
|
+
# TODO: This makes 1 database call per record to be saved. A bulk-insert would speed
|
33
|
+
# things up considerably.
|
34
|
+
#++
|
35
|
+
def save!
|
36
|
+
retrieve_feed
|
37
|
+
.yield_self { |feed_body| parse_feed(feed_body) }
|
38
|
+
.map { |rate_date_hash| rates_from(rate_date_hash, date_from(rate_date_hash)) }
|
39
|
+
.flatten
|
40
|
+
.map(&:save)
|
41
|
+
nil
|
42
|
+
rescue StandardError
|
43
|
+
raise ExchangeRate::RetrievalFailedError
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
##
|
49
|
+
# Perform the HTTP request to grab the feed contents.
|
50
|
+
#
|
51
|
+
# Returns the response body as a String.
|
52
|
+
def retrieve_feed
|
53
|
+
uri = URI.parse(@feed_url)
|
54
|
+
Net::HTTP.get_response(uri).body
|
55
|
+
end
|
56
|
+
|
57
|
+
##
|
58
|
+
# Parse the ECB feed XML into an array of feed ::Hash objects.
|
59
|
+
#
|
60
|
+
# feed_body - The XML ECB feed document as a String.
|
61
|
+
#
|
62
|
+
# Returns an array of currency/date/rate ::Hash objects, grouped by date (see #rate_array_from).
|
63
|
+
def parse_feed(feed_body)
|
64
|
+
Hash.from_xml(feed_body)
|
65
|
+
.yield_self { |feed_hash| rate_array_from(feed_hash) }
|
66
|
+
end
|
67
|
+
|
68
|
+
##
|
69
|
+
# Extract the array of FX rates from the XML document.
|
70
|
+
#
|
71
|
+
# feed_hash - A ::Hash representation of the XML ECB feed document.
|
72
|
+
#
|
73
|
+
# Returns An array of date/rate/currency ::Hash objects, grouped by date.
|
74
|
+
def rate_array_from(feed_hash)
|
75
|
+
feed_hash['Envelope']['Cube']['Cube']
|
76
|
+
end
|
77
|
+
|
78
|
+
##
|
79
|
+
# Extract the effective date of an FX rate.
|
80
|
+
#
|
81
|
+
# rate_date_hash - A single currency/date/rate ::Hash object.
|
82
|
+
#
|
83
|
+
# Returns the effective ::Date of the FX rate.
|
84
|
+
def date_from(rate_date_hash)
|
85
|
+
Date.parse(rate_date_hash['time'])
|
86
|
+
end
|
87
|
+
|
88
|
+
##
|
89
|
+
# Builds an array of ExchangeRate::CurrencyRate objects.
|
90
|
+
#
|
91
|
+
# rate_date_array - An array of currency/rate ::Hash objects
|
92
|
+
# rate_effective_date - The effective ::Date for FX rates in the rate_date_hash.
|
93
|
+
def rates_from(rate_date_array, rate_effective_date)
|
94
|
+
rate_date_array['Cube'].map do |currency_rate_hash|
|
95
|
+
create_or_update_currency_rate(currency: currency_rate_hash['currency'],
|
96
|
+
value_in_euro: currency_rate_hash['rate'].to_f,
|
97
|
+
date_of_rate: rate_effective_date)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
##
|
102
|
+
# Creates or updates the local cache of the currency/rate/date tuple
|
103
|
+
#
|
104
|
+
# currency - The currency short-code, e.g. 'GBP'.
|
105
|
+
# value_in_euro - The value of one unit (e.g. $1) in Euro.
|
106
|
+
# date_of_rate - The date this value applies to.
|
107
|
+
#
|
108
|
+
# Returns true if the record saved
|
109
|
+
# Raises ExchangeRate::RetrievalFailedError if the record could not be saved
|
110
|
+
def create_or_update_currency_rate(currency:, value_in_euro:, date_of_rate:)
|
111
|
+
rate = CurrencyRate.find_or_new(currency: currency, date_of_rate: date_of_rate)
|
112
|
+
rate.value_in_euro = value_in_euro
|
113
|
+
rate.tap(&:save)
|
114
|
+
rescue Sequel::ValidationFailed, Sequel::HookFailed
|
115
|
+
raise ExchangeRate::RetrievalFailedError
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
metadata
ADDED
@@ -0,0 +1,247 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fx_exchange_rate
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ryan Brooks
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-08-28 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.16'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.16'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: database_cleaner
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.7.0
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.7.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rubocop
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 0.58.2
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 0.58.2
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: simplecov
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 0.16.1
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 0.16.1
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: vcr
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 4.0.0
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 4.0.0
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: webmock
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 3.4.2
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: 3.4.2
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: pry
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: 0.11.3
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: 0.11.3
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: activesupport
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - "~>"
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: 5.2.1
|
146
|
+
type: :runtime
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - "~>"
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: 5.2.1
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: sequel
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - "~>"
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: 5.11.0
|
160
|
+
type: :runtime
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - "~>"
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: 5.11.0
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: sqlite3
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - "~>"
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: 1.3.13
|
174
|
+
type: :runtime
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - "~>"
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: 1.3.13
|
181
|
+
description: ExchangeRate uses a cache for FX rates so you are not dependent on a
|
182
|
+
constant connection to your FX rate provider of choice, and allow custom FX rate
|
183
|
+
providers to be added with ease.
|
184
|
+
email:
|
185
|
+
- ryan@ryanbrooks.co.uk
|
186
|
+
executables: []
|
187
|
+
extensions: []
|
188
|
+
extra_rdoc_files: []
|
189
|
+
files:
|
190
|
+
- ".gitignore"
|
191
|
+
- ".rdoc_options"
|
192
|
+
- ".rspec"
|
193
|
+
- ".rubocop.yml"
|
194
|
+
- ".ruby-version"
|
195
|
+
- ".travis.yml"
|
196
|
+
- CODE_OF_CONDUCT.md
|
197
|
+
- Gemfile
|
198
|
+
- Gemfile.lock
|
199
|
+
- LICENSE.txt
|
200
|
+
- README.md
|
201
|
+
- Rakefile
|
202
|
+
- bin/console
|
203
|
+
- bin/setup
|
204
|
+
- fixtures/vcr_cassettes/ExchangeRate/_retrieve/fetches_the_feed_and_stores_the_values_locally.yml
|
205
|
+
- fixtures/vcr_cassettes/ExchangeRate/_retrieve/overwrites_existing_values.yml
|
206
|
+
- fixtures/vcr_cassettes/ExchangeRate/_retrieve/raises_ExchangeRate_RetrievalFailed_when_the_the_feed_cannot_be_retrieved.yml
|
207
|
+
- fixtures/vcr_cassettes/ExchangeRate_RateSources_ECBRateRetriever/_save_/raises_when_the_feed_content_is_invalid.yml
|
208
|
+
- fixtures/vcr_cassettes/ExchangeRate_RateSources_ECBRateRetriever/_save_/retrieves_the_feed.yml
|
209
|
+
- fx_exchange_rate.gemspec
|
210
|
+
- lib/exchange_rate.rb
|
211
|
+
- lib/exchange_rate/configuration.rb
|
212
|
+
- lib/exchange_rate/currency_converter.rb
|
213
|
+
- lib/exchange_rate/currency_rate.rb
|
214
|
+
- lib/exchange_rate/database_connection.rb
|
215
|
+
- lib/exchange_rate/db/migrate/20180827184400_create_currency_rates.rb
|
216
|
+
- lib/exchange_rate/initializers/sequel.rb
|
217
|
+
- lib/exchange_rate/missing_rate_error.rb
|
218
|
+
- lib/exchange_rate/module_methods.rb
|
219
|
+
- lib/exchange_rate/rate_sources.rb
|
220
|
+
- lib/exchange_rate/rate_sources/e_c_b_rate_retriever.rb
|
221
|
+
- lib/exchange_rate/retrieval_failed_error.rb
|
222
|
+
- lib/exchange_rate/version.rb
|
223
|
+
homepage:
|
224
|
+
licenses:
|
225
|
+
- MIT
|
226
|
+
metadata: {}
|
227
|
+
post_install_message:
|
228
|
+
rdoc_options: []
|
229
|
+
require_paths:
|
230
|
+
- lib
|
231
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
232
|
+
requirements:
|
233
|
+
- - ">="
|
234
|
+
- !ruby/object:Gem::Version
|
235
|
+
version: '0'
|
236
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
237
|
+
requirements:
|
238
|
+
- - ">="
|
239
|
+
- !ruby/object:Gem::Version
|
240
|
+
version: '0'
|
241
|
+
requirements: []
|
242
|
+
rubyforge_project:
|
243
|
+
rubygems_version: 2.7.6
|
244
|
+
signing_key:
|
245
|
+
specification_version: 4
|
246
|
+
summary: Retrieve and cache historical foreign exchange rates
|
247
|
+
test_files: []
|