currency_exchanger 0.1.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 +9 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +59 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exchanger.gemspec +32 -0
- data/exe/exchanger +7 -0
- data/lib/exchanger.rb +10 -0
- data/lib/exchanger/exchange_rate.rb +93 -0
- data/lib/exchanger/version.rb +3 -0
- metadata +143 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ad2908816197413d53b48d526817f5c1d84046f7
|
4
|
+
data.tar.gz: 763af24acb5c5afce9506d206f726f696f95402f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d2aa85948762e107a10371192455cbca55f4918c5539161ca9ddd3c7e91dc5f52290026d752aca67fb6610e47f9552ec2fd7da63eff53522d3d2dd6b147cdd82
|
7
|
+
data.tar.gz: 1d79f769e0a84a99fe8ea7a3192ad5899418e7b472184e266bb27e255cc1116e65776c9f14c2e89d0371dd7a371964f9a582689b8dde70b935e1500f8e0f93c9
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2017 ann_ann
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
# Exchanger
|
2
|
+
|
3
|
+
Gem for exchanging USD to EUROs using ECB rates:
|
4
|
+
|
5
|
+
|
6
|
+
|
7
|
+
https://sdw.ecb.europa.eu/quickview.do?SERIES_KEY=120.EXR.D.USD.EUR.SP00.A
|
8
|
+
|
9
|
+
|
10
|
+
It fetches csv data and stores to local sqllite db
|
11
|
+
|
12
|
+
## Installation
|
13
|
+
|
14
|
+
Add this line to your application's Gemfile:
|
15
|
+
|
16
|
+
gem 'currency_exchanger'
|
17
|
+
|
18
|
+
And then execute:
|
19
|
+
|
20
|
+
$ bundle
|
21
|
+
|
22
|
+
Or install it yourself as:
|
23
|
+
|
24
|
+
$ gem install currency_exchanger
|
25
|
+
|
26
|
+
## Usage
|
27
|
+
|
28
|
+
You can test it just from terminal after gem installed. Example:
|
29
|
+
|
30
|
+
|
31
|
+
$ ./exe/exchanger AMOUNT DATES_SEPARATED_BY_COMMA
|
32
|
+
|
33
|
+
|
34
|
+
$ ./exe/exchanger 100 2017-01-16
|
35
|
+
|
36
|
+
|
37
|
+
Example of gem using:
|
38
|
+
|
39
|
+
Exchanger.exchange(100, Date.today)
|
40
|
+
|
41
|
+
|
42
|
+
=> 109.94
|
43
|
+
|
44
|
+
|
45
|
+
Exchanger.exchange(100, [Date.yesterday, Date.today])
|
46
|
+
|
47
|
+
|
48
|
+
=> [109.94, 110.02]
|
49
|
+
|
50
|
+
|
51
|
+
## Contributing
|
52
|
+
|
53
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/ann-ann/exchanger.
|
54
|
+
|
55
|
+
|
56
|
+
## License
|
57
|
+
|
58
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
59
|
+
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "exchanger"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/exchanger.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'exchanger/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "currency_exchanger"
|
8
|
+
spec.version = Exchanger::VERSION
|
9
|
+
spec.authors = ["ann_ann"]
|
10
|
+
spec.email = ["saddy666@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Currency exchanger gem}
|
13
|
+
spec.homepage = "https://github.com/ann-ann/exchanger"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
17
|
+
f.match(%r{^(test|spec|features)/})
|
18
|
+
end
|
19
|
+
spec.bindir = "exe"
|
20
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
|
+
spec.require_paths = ["lib"]
|
22
|
+
|
23
|
+
spec.required_ruby_version = '~> 2.3'
|
24
|
+
|
25
|
+
spec.add_dependency "sqlite3", "~> 1.3"
|
26
|
+
spec.add_dependency "activerecord", "~> 5"
|
27
|
+
|
28
|
+
spec.add_development_dependency "bundler", "~> 1.13"
|
29
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
30
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
31
|
+
spec.add_development_dependency "pry"
|
32
|
+
end
|
data/exe/exchanger
ADDED
data/lib/exchanger.rb
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
require "active_record"
|
2
|
+
require "csv"
|
3
|
+
require "open-uri"
|
4
|
+
|
5
|
+
ActiveRecord::Base.logger = Logger.new(STDERR)
|
6
|
+
|
7
|
+
ActiveRecord::Base.establish_connection(
|
8
|
+
adapter: "sqlite3",
|
9
|
+
database: "/tmp/exchanger.sqlite3"
|
10
|
+
)
|
11
|
+
|
12
|
+
ActiveRecord::Schema.define do
|
13
|
+
unless ActiveRecord::Base.connection.tables.include? "exchange_rates"
|
14
|
+
create_table :exchange_rates do |t|
|
15
|
+
t.date :date, null: false, index: true
|
16
|
+
t.decimal :rate, scale: 2, precision: 8
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
module Exchanger
|
22
|
+
class ExchangeRate < ActiveRecord::Base
|
23
|
+
DATA_SOURCE = "https://sdw.ecb.europa.eu/quickviewexport.do?SERIES_KEY=120.EXR.D.USD.EUR.SP00.A&type=csv".freeze
|
24
|
+
DATE_NORMALIZER = lambda { |date| Date.parse(date) rescue nil }
|
25
|
+
|
26
|
+
validates :date, uniqueness: true, presence: true
|
27
|
+
validates :rate, numericality: { greater_than: 0 }, presence: true
|
28
|
+
|
29
|
+
class << self
|
30
|
+
private
|
31
|
+
|
32
|
+
# Determine previous or next business day
|
33
|
+
def skip_weekends(date, inc)
|
34
|
+
while date.on_weekend? do
|
35
|
+
date += inc
|
36
|
+
end
|
37
|
+
date
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Seeds database with exchange rates from ECB.
|
42
|
+
#
|
43
|
+
# As per https://www.ecb.europa.eu/stats/exchange/eurofxref/html/index.en.html, the reference rates are usually
|
44
|
+
# updated around 16:00 CET on every working day, except on TARGET closing days (holidays like Christmas).
|
45
|
+
# They are based on a regular daily concertation procedure between central banks across Europe, which normally
|
46
|
+
# takes place at 14:15 CET(but updates arounf 16pm)
|
47
|
+
#
|
48
|
+
# We implement simplest possible caching based on ECD data update rules mentioned above.
|
49
|
+
def self.seed
|
50
|
+
last_available_record = ExchangeRate.order(:date).last
|
51
|
+
|
52
|
+
if (last_available_record&.date == skip_weekends(Date.today.in_time_zone('Berlin').to_date, -1)) && Time.now.in_time_zone('Berlin').hour >= 16
|
53
|
+
return
|
54
|
+
end
|
55
|
+
encoded_url = URI.encode(DATA_SOURCE)
|
56
|
+
data = open(URI.parse(encoded_url)).read
|
57
|
+
|
58
|
+
CSV.parse(data)[5..-1].each do |date, rate|
|
59
|
+
next if rate == '-'
|
60
|
+
ExchangeRate.find_or_create_by(date: date, rate: rate.to_f)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.exchange(amount:, dates:)
|
65
|
+
amount = amount.to_f
|
66
|
+
dates = [*dates].map(&DATE_NORMALIZER)
|
67
|
+
|
68
|
+
dates.map do |date|
|
69
|
+
if date
|
70
|
+
if rate_record = ExchangeRate.where(date: date).first
|
71
|
+
# Regular business day, we have all the data.
|
72
|
+
(rate_record.rate * amount).to_f
|
73
|
+
elsif date.on_weekend? && next_business_day = skip_weekends(date.in_time_zone('Berlin').to_date, 1)
|
74
|
+
# Weekends. When you send exchange request on a weekend, it will be exchanges on the next business day.
|
75
|
+
# Note that this is in CET timezone as per ECB rules. Sorry, no luck exchanging on Moday from Sydney.
|
76
|
+
|
77
|
+
# I wish I knew but I really don't
|
78
|
+
return nil if next_business_day.future?
|
79
|
+
|
80
|
+
rate_record = ExchangeRate.where(date: next_business_day).first
|
81
|
+
(rate_record.rate * amount).to_f
|
82
|
+
else
|
83
|
+
# Sorry, don't know why you couldn't exchange euros on 2012-05-01. Sometimes ECB doesn't have data.
|
84
|
+
# Also handles dates that are in future.
|
85
|
+
"No rate for this date"
|
86
|
+
end
|
87
|
+
else
|
88
|
+
"Invalid date passed"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
metadata
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: currency_exchanger
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- ann_ann
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-01-16 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: sqlite3
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activerecord
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '5'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '5'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.13'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.13'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '10.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '10.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '3.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '3.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: pry
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
description:
|
98
|
+
email:
|
99
|
+
- saddy666@gmail.com
|
100
|
+
executables:
|
101
|
+
- exchanger
|
102
|
+
extensions: []
|
103
|
+
extra_rdoc_files: []
|
104
|
+
files:
|
105
|
+
- ".gitignore"
|
106
|
+
- ".rspec"
|
107
|
+
- ".travis.yml"
|
108
|
+
- Gemfile
|
109
|
+
- LICENSE.txt
|
110
|
+
- README.md
|
111
|
+
- Rakefile
|
112
|
+
- bin/console
|
113
|
+
- bin/setup
|
114
|
+
- exchanger.gemspec
|
115
|
+
- exe/exchanger
|
116
|
+
- lib/exchanger.rb
|
117
|
+
- lib/exchanger/exchange_rate.rb
|
118
|
+
- lib/exchanger/version.rb
|
119
|
+
homepage: https://github.com/ann-ann/exchanger
|
120
|
+
licenses:
|
121
|
+
- MIT
|
122
|
+
metadata: {}
|
123
|
+
post_install_message:
|
124
|
+
rdoc_options: []
|
125
|
+
require_paths:
|
126
|
+
- lib
|
127
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '2.3'
|
132
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
133
|
+
requirements:
|
134
|
+
- - ">="
|
135
|
+
- !ruby/object:Gem::Version
|
136
|
+
version: '0'
|
137
|
+
requirements: []
|
138
|
+
rubyforge_project:
|
139
|
+
rubygems_version: 2.6.8
|
140
|
+
signing_key:
|
141
|
+
specification_version: 4
|
142
|
+
summary: Currency exchanger gem
|
143
|
+
test_files: []
|