simple_currency 1.1.1 → 1.1.2
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.
- data/VERSION +1 -1
- data/lib/simple_currency/currency_convertible.rb +53 -13
- data/simple_currency.gemspec +3 -3
- data/spec/simple_currency_spec.rb +32 -6
- metadata +20 -3
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.1.
|
1
|
+
1.1.2
|
@@ -18,6 +18,7 @@ module CurrencyConvertible
|
|
18
18
|
# Historical exchange lookup
|
19
19
|
def at(exchange = nil)
|
20
20
|
begin
|
21
|
+
|
21
22
|
@exchange_date = exchange.send(:to_date)
|
22
23
|
rescue
|
23
24
|
raise "Must use 'at' with a time or date object"
|
@@ -85,9 +86,16 @@ module CurrencyConvertible
|
|
85
86
|
# Calls Xurrency API to perform the exchange without a specific date (assuming today)
|
86
87
|
#
|
87
88
|
def call_xurrency_api(original, target, amount)
|
89
|
+
|
90
|
+
if amount > 999_999_999 # Xurrency API does not support numbers bigger than this
|
91
|
+
amount = 1
|
92
|
+
multiplier = self.abs # Save a multiplier to apply it to the result later
|
93
|
+
end
|
94
|
+
|
88
95
|
api_url = "http://xurrency.com/api/#{[original, target, amount].join('/')}"
|
89
96
|
uri = URI.parse(api_url)
|
90
97
|
|
98
|
+
|
91
99
|
retries = 10
|
92
100
|
json_response = nil
|
93
101
|
begin
|
@@ -100,11 +108,14 @@ module CurrencyConvertible
|
|
100
108
|
end
|
101
109
|
|
102
110
|
return nil unless json_response && parsed_response = Crack::JSON.parse(json_response)
|
103
|
-
|
111
|
+
if parsed_response['status'] != 'ok'
|
112
|
+
raise CurrencyNotFoundException if parsed_response['message'] =~ /currencies/
|
113
|
+
raise parsed_response['message']
|
114
|
+
end
|
104
115
|
|
105
116
|
result = parsed_response['result']['value'].to_f
|
106
117
|
cache_rate(original, target, (result / amount))
|
107
|
-
result
|
118
|
+
result * (multiplier || 1)
|
108
119
|
end
|
109
120
|
|
110
121
|
# Calls Xavier API to perform the exchange with a specific date.
|
@@ -123,9 +134,11 @@ module CurrencyConvertible
|
|
123
134
|
date = @exchange_date
|
124
135
|
args = [date.year, date.month.to_s.rjust(2, '0'), date.day.to_s.rjust(2,'0')]
|
125
136
|
api_url = "http://api.finance.xaviermedia.com/api/#{args.join('/')}.xml"
|
137
|
+
|
126
138
|
uri = URI.parse(api_url)
|
127
139
|
|
128
140
|
retries = 10
|
141
|
+
not_found_retries=2
|
129
142
|
xml_response = nil
|
130
143
|
begin
|
131
144
|
Timeout::timeout(1){
|
@@ -136,9 +149,21 @@ module CurrencyConvertible
|
|
136
149
|
rescue Timeout::Error
|
137
150
|
retries -= 1
|
138
151
|
retries > 0 ? sleep(0.42) && retry : raise
|
152
|
+
rescue OpenURI::HTTPError # Try to fetch one day and 2 days earlier
|
153
|
+
not_found_retries -= 1
|
154
|
+
if not_found_retries >= 0
|
155
|
+
date = (Time.parse(date.to_s) - 86400).send(:to_date)
|
156
|
+
args = [date.year, date.month.to_s.rjust(2, '0'), date.day.to_s.rjust(2,'0')]
|
157
|
+
api_url = "http://api.finance.xaviermedia.com/api/#{args.join('/')}.xml"
|
158
|
+
uri = URI.parse(api_url)
|
159
|
+
retry
|
160
|
+
else
|
161
|
+
raise "404 Not Found"
|
162
|
+
end
|
139
163
|
end
|
140
164
|
|
141
|
-
return nil unless xml_response && parsed_response = Crack::XML.parse(xml_response)
|
165
|
+
return nil unless xml_response && parsed_response = Crack::XML.parse(xml_response)
|
166
|
+
parsed_response = parsed_response["xavierresponse"]["exchange_rates"]["fx"]
|
142
167
|
|
143
168
|
# Cache successful XML response for later reuse
|
144
169
|
if defined?(Rails)
|
@@ -148,21 +173,24 @@ module CurrencyConvertible
|
|
148
173
|
|
149
174
|
# Calculate the exchange rate from the XML
|
150
175
|
if parsed_response.first['basecurrency'].downcase == original
|
151
|
-
rate = parsed_response
|
176
|
+
rate = parse_rate(parsed_response, target)
|
152
177
|
elsif parsed_response.first['basecurrency'].downcase == target
|
153
|
-
|
154
|
-
rate = 1 / target_rate if target_rate
|
178
|
+
rate = 1 / parse_rate(parsed_response, original)
|
155
179
|
else
|
156
|
-
original_rate = parsed_response
|
157
|
-
target_rate = parsed_response
|
180
|
+
original_rate = parse_rate(parsed_response, original)
|
181
|
+
target_rate = parse_rate(parsed_response, target)
|
158
182
|
rate = target_rate / original_rate
|
159
183
|
end
|
160
184
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
185
|
+
cache_rate(original, target, rate)
|
186
|
+
amount * rate
|
187
|
+
end
|
188
|
+
|
189
|
+
def parse_rate(parsed_response, currency)
|
190
|
+
rate = parsed_response.select{|element| element["currency_code"].downcase == currency}
|
191
|
+
raise CurrencyNotFoundException, "#{currency} is not a valid currency" unless rate && rate.is_a?(Array) && rate.first
|
192
|
+
raise NoRatesFoundException, "no exchange rate found for #{currency}" unless rate.first['rate'] && rate.first['rate'].to_f > 0
|
193
|
+
rate.first['rate'].to_f
|
166
194
|
end
|
167
195
|
|
168
196
|
# Caches currency methods to avoid method missing abuse.
|
@@ -224,3 +252,15 @@ module CurrencyConvertible
|
|
224
252
|
end
|
225
253
|
|
226
254
|
end
|
255
|
+
|
256
|
+
##
|
257
|
+
# Custom Exceptions
|
258
|
+
##
|
259
|
+
|
260
|
+
class CurrencyNotFoundException < StandardError
|
261
|
+
end
|
262
|
+
|
263
|
+
class NoRatesFoundException < StandardError
|
264
|
+
end
|
265
|
+
|
266
|
+
|
data/simple_currency.gemspec
CHANGED
@@ -5,7 +5,7 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{simple_currency}
|
8
|
-
s.version = "1.1.
|
8
|
+
s.version = "1.1.2"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Oriol Gual", "Josep M. Bach", "Josep Jaume Rey"]
|
@@ -40,7 +40,7 @@ Gem::Specification.new do |s|
|
|
40
40
|
s.homepage = %q{http://github.com/codegram/simple_currency}
|
41
41
|
s.rdoc_options = ["--charset=UTF-8"]
|
42
42
|
s.require_paths = ["lib"]
|
43
|
-
s.rubygems_version = %q{1.3.
|
43
|
+
s.rubygems_version = %q{1.3.7}
|
44
44
|
s.summary = %q{A really simple currency converter using the Xurrency and XavierMedia APIs.}
|
45
45
|
s.test_files = [
|
46
46
|
"spec/simple_currency_spec.rb",
|
@@ -51,7 +51,7 @@ Gem::Specification.new do |s|
|
|
51
51
|
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
52
52
|
s.specification_version = 3
|
53
53
|
|
54
|
-
if Gem::Version.new(Gem::
|
54
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
55
55
|
s.add_runtime_dependency(%q<crack>, [">= 0.1.8"])
|
56
56
|
s.add_development_dependency(%q<jeweler>, [">= 1.4.0"])
|
57
57
|
s.add_development_dependency(%q<rspec>, [">= 2.0.0.beta.20"])
|
@@ -37,8 +37,8 @@ describe "SimpleCurrency" do
|
|
37
37
|
mock_xurrency_api('usd', 'xxx', 1, 1.5, :fail_with => "The currencies are not valid")
|
38
38
|
mock_xurrency_api('usd', 'eur', 1_000_000_000, 1.5, :fail_with => "The amount should be between 0 and 999999999")
|
39
39
|
|
40
|
-
expect {1.usd.to_xxx}.to raise_error(
|
41
|
-
expect {1_000_000_000.usd.to_eur}.
|
40
|
+
expect {1.usd.to_xxx}.to raise_error(CurrencyNotFoundException)
|
41
|
+
expect {1_000_000_000.usd.to_eur}.to_not raise_error
|
42
42
|
end
|
43
43
|
|
44
44
|
it "handles a negative value returning a negative as well" do
|
@@ -47,6 +47,12 @@ describe "SimpleCurrency" do
|
|
47
47
|
-1.usd.to_eur.should == -1.5
|
48
48
|
end
|
49
49
|
|
50
|
+
it "handles big amounts" do
|
51
|
+
mock_xurrency_api('usd', 'eur', 1, 1.5)
|
52
|
+
|
53
|
+
1_000_000_000.usd.to_eur.should == 1_500_000_000
|
54
|
+
end
|
55
|
+
|
50
56
|
end
|
51
57
|
|
52
58
|
context "using XavierMedia API for exchange with historical date" do
|
@@ -67,12 +73,32 @@ describe "SimpleCurrency" do
|
|
67
73
|
2.gbp.at(the_past).to_usd.should == 3.07
|
68
74
|
end
|
69
75
|
|
76
|
+
it "retries yesterday and two days ago if no data found (could be a holiday)" do
|
77
|
+
mock_xavier_api(the_past, :fail => true) # It's a holiday
|
78
|
+
mock_xavier_api(the_past - 86400, :fail => true) # It's a holiday
|
79
|
+
mock_xavier_api(the_past - 86400 - 86400) # It's a holiday
|
80
|
+
|
81
|
+
2.eur.at(the_past).to_usd.should == 2.54
|
82
|
+
end
|
83
|
+
|
70
84
|
it "raises an error when no data available" do
|
71
|
-
mock_xavier_api(the_past, :fail => true) #
|
72
|
-
mock_xavier_api(
|
85
|
+
mock_xavier_api(the_past, :fail => true) # It's a holiday
|
86
|
+
mock_xavier_api(the_past - 86400, :fail => true) # It's a holiday
|
87
|
+
mock_xavier_api(the_past - 86400 - 86400, :fail => true) # It's a holiday
|
88
|
+
|
89
|
+
expect {1.usd.at(the_past).to_eur}.to raise_error("404 Not Found")
|
90
|
+
end
|
91
|
+
|
92
|
+
it "raises a CurrencyNotFoundException given an invalid currency" do
|
93
|
+
mock_xavier_api(the_past)
|
94
|
+
|
95
|
+
expect {1.usd.at(the_past).to_xxx}.to raise_error(CurrencyNotFoundException)
|
96
|
+
end
|
97
|
+
|
98
|
+
it "raises a NoRatesFoundException if rate is 0.0 or non-existant" do
|
99
|
+
mock_xavier_api(the_past)
|
73
100
|
|
74
|
-
expect {1.
|
75
|
-
expect {1.usd.at(the_ancient_past).to_eur}.to raise_error(OpenURI::HTTPError)
|
101
|
+
expect {1.eur.at(the_past).to_rol}.to raise_error(NoRatesFoundException)
|
76
102
|
end
|
77
103
|
|
78
104
|
end
|
metadata
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: simple_currency
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
+
hash: 23
|
4
5
|
prerelease: false
|
5
6
|
segments:
|
6
7
|
- 1
|
7
8
|
- 1
|
8
|
-
-
|
9
|
-
version: 1.1.
|
9
|
+
- 2
|
10
|
+
version: 1.1.2
|
10
11
|
platform: ruby
|
11
12
|
authors:
|
12
13
|
- Oriol Gual
|
@@ -23,9 +24,11 @@ dependencies:
|
|
23
24
|
name: crack
|
24
25
|
prerelease: false
|
25
26
|
requirement: &id001 !ruby/object:Gem::Requirement
|
27
|
+
none: false
|
26
28
|
requirements:
|
27
29
|
- - ">="
|
28
30
|
- !ruby/object:Gem::Version
|
31
|
+
hash: 11
|
29
32
|
segments:
|
30
33
|
- 0
|
31
34
|
- 1
|
@@ -37,9 +40,11 @@ dependencies:
|
|
37
40
|
name: jeweler
|
38
41
|
prerelease: false
|
39
42
|
requirement: &id002 !ruby/object:Gem::Requirement
|
43
|
+
none: false
|
40
44
|
requirements:
|
41
45
|
- - ">="
|
42
46
|
- !ruby/object:Gem::Version
|
47
|
+
hash: 7
|
43
48
|
segments:
|
44
49
|
- 1
|
45
50
|
- 4
|
@@ -51,9 +56,11 @@ dependencies:
|
|
51
56
|
name: rspec
|
52
57
|
prerelease: false
|
53
58
|
requirement: &id003 !ruby/object:Gem::Requirement
|
59
|
+
none: false
|
54
60
|
requirements:
|
55
61
|
- - ">="
|
56
62
|
- !ruby/object:Gem::Version
|
63
|
+
hash: 62196427
|
57
64
|
segments:
|
58
65
|
- 2
|
59
66
|
- 0
|
@@ -67,9 +74,11 @@ dependencies:
|
|
67
74
|
name: fakeweb
|
68
75
|
prerelease: false
|
69
76
|
requirement: &id004 !ruby/object:Gem::Requirement
|
77
|
+
none: false
|
70
78
|
requirements:
|
71
79
|
- - ">="
|
72
80
|
- !ruby/object:Gem::Version
|
81
|
+
hash: 27
|
73
82
|
segments:
|
74
83
|
- 1
|
75
84
|
- 3
|
@@ -81,9 +90,11 @@ dependencies:
|
|
81
90
|
name: rails
|
82
91
|
prerelease: false
|
83
92
|
requirement: &id005 !ruby/object:Gem::Requirement
|
93
|
+
none: false
|
84
94
|
requirements:
|
85
95
|
- - ">="
|
86
96
|
- !ruby/object:Gem::Version
|
97
|
+
hash: 7
|
87
98
|
segments:
|
88
99
|
- 3
|
89
100
|
- 0
|
@@ -95,9 +106,11 @@ dependencies:
|
|
95
106
|
name: bundler
|
96
107
|
prerelease: false
|
97
108
|
requirement: &id006 !ruby/object:Gem::Requirement
|
109
|
+
none: false
|
98
110
|
requirements:
|
99
111
|
- - ">="
|
100
112
|
- !ruby/object:Gem::Version
|
113
|
+
hash: 23
|
101
114
|
segments:
|
102
115
|
- 1
|
103
116
|
- 0
|
@@ -144,23 +157,27 @@ rdoc_options:
|
|
144
157
|
require_paths:
|
145
158
|
- lib
|
146
159
|
required_ruby_version: !ruby/object:Gem::Requirement
|
160
|
+
none: false
|
147
161
|
requirements:
|
148
162
|
- - ">="
|
149
163
|
- !ruby/object:Gem::Version
|
164
|
+
hash: 3
|
150
165
|
segments:
|
151
166
|
- 0
|
152
167
|
version: "0"
|
153
168
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
169
|
+
none: false
|
154
170
|
requirements:
|
155
171
|
- - ">="
|
156
172
|
- !ruby/object:Gem::Version
|
173
|
+
hash: 3
|
157
174
|
segments:
|
158
175
|
- 0
|
159
176
|
version: "0"
|
160
177
|
requirements: []
|
161
178
|
|
162
179
|
rubyforge_project:
|
163
|
-
rubygems_version: 1.3.
|
180
|
+
rubygems_version: 1.3.7
|
164
181
|
signing_key:
|
165
182
|
specification_version: 3
|
166
183
|
summary: A really simple currency converter using the Xurrency and XavierMedia APIs.
|