simple_currency 1.1.1 → 1.1.2

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION CHANGED
@@ -1 +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
- raise parsed_response['message'] if parsed_response['status'] != 'ok'
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)["xavierresponse"]["exchange_rates"]["fx"]
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.select{|element| element["currency_code"].downcase == target}.first['rate'].to_f
176
+ rate = parse_rate(parsed_response, target)
152
177
  elsif parsed_response.first['basecurrency'].downcase == target
153
- target_rate = parsed_response.select{|element| element["currency_code"].downcase == original}.first['rate'].to_f
154
- rate = 1 / target_rate if target_rate
178
+ rate = 1 / parse_rate(parsed_response, original)
155
179
  else
156
- original_rate = parsed_response.select{|element| element["currency_code"].downcase == original}.first['rate'].to_f
157
- target_rate = parsed_response.select{|element| element["currency_code"].downcase == target}.first['rate'].to_f
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
- if rate
162
- cache_rate(original, target, rate)
163
- return amount * rate
164
- end
165
- nil
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
+
@@ -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.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.6}
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::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
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("The currencies are not valid")
41
- expect {1_000_000_000.usd.to_eur}.to raise_error("The amount should be between 0 and 999999999")
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) # Currency does not exist!
72
- mock_xavier_api(the_ancient_past, :fail => true) # Too old!
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.usd.at(the_past).to_xxx}.to raise_error(OpenURI::HTTPError)
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
- - 1
9
- version: 1.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.6
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.