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 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.