money-open-exchange-rates 0.0.7 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,7 @@
1
1
  # Money Open Exchange Rates
2
2
 
3
- A gem that calculates the exchange rate using published rates from [open-exchange-rates](http://josscrowcroft.github.com/open-exchange-rates/)
3
+ A gem that calculates the exchange rate using published rates from
4
+ [open-exchange-rates](http://josscrowcroft.github.com/open-exchange-rates/)
4
5
 
5
6
  ## Usage
6
7
 
@@ -8,14 +9,33 @@ A gem that calculates the exchange rate using published rates from [open-exchang
8
9
  require 'money/bank/open_exchange_rates_bank'
9
10
  moe = Money::Bank::OpenExchangeRatesBank.new
10
11
  moe.cache = 'path/to/file/cache'
12
+ moe.app_id = 'your app id from https://openexchangerates.org/signup'
11
13
  moe.update_rates
12
14
 
13
15
  Money.default_bank = moe
14
16
  ```
15
17
 
18
+ You can also provide a Proc as a cache to provide your own caching mechanism
19
+ perhaps with Redis or just a thread safe `Hash` (global). For example:
20
+
21
+ ```ruby
22
+ moe.cache = Proc.new do |v|
23
+ key = 'money:exchange_rates']
24
+ if v
25
+ Thread.current[key] = v
26
+ else
27
+ Thread.current[key]
28
+ end
29
+ end
30
+ ```
31
+
16
32
  ## Tests
17
33
 
18
- bundle exec ruby test/open_exchange_rates_bank_test.rb
34
+ As of the end of August 2012 all requests to the Open Exchange Rates API must
35
+ have a valid app_id. You can place your own key on a file named TEST_APP_ID and
36
+ then run:
37
+
38
+ ```bundle exec ruby test/open_exchange_rates_bank_test.rb```
19
39
 
20
40
  ## Refs
21
41
 
@@ -31,6 +51,7 @@ Money.default_bank = moe
31
51
  * [Kevin Ball](https://github.com/kball)
32
52
  * [Michael Morris](https://github.com/mtcmorris)
33
53
 
54
+
34
55
  ## License
35
56
  The MIT License
36
57
 
@@ -1,18 +1,20 @@
1
1
  # encoding: UTF-8
2
2
  require 'open-uri'
3
- require 'yajl'
3
+ require 'multi_json'
4
4
  require 'money'
5
5
 
6
6
  class Money
7
7
  module Bank
8
8
  class InvalidCache < StandardError ; end
9
9
 
10
+ class NoAppId < StandardError ; end
11
+
10
12
  class OpenExchangeRatesBank < Money::Bank::VariableExchange
11
13
 
12
14
  OER_URL = 'http://openexchangerates.org/latest.json'
13
15
 
14
- attr_accessor :cache
15
- attr_reader :doc, :oer_rates, :rates_source
16
+ attr_accessor :cache, :app_id
17
+ attr_reader :doc, :oer_rates
16
18
 
17
19
  def update_rates
18
20
  exchange_rates.each do |exchange_rate|
@@ -23,25 +25,11 @@ class Money
23
25
  end
24
26
  end
25
27
 
26
- def read_from_url
27
- open(OER_URL).read
28
- end
29
-
30
- def has_valid_rates?(text)
31
- parsed = Yajl::Parser.parse(text)
32
- parsed && parsed.has_key?('rates')
33
- rescue Yajl::ParseError
34
- false
35
- end
36
-
37
-
38
28
  def save_rates
39
29
  raise InvalidCache unless cache
40
- new_text = read_from_url
41
- if has_valid_rates?(new_text)
42
- open(cache, 'w') do |f|
43
- f.write(new_text)
44
- end
30
+ text = read_from_url
31
+ if has_valid_rates?(text)
32
+ store_in_cache(text)
45
33
  end
46
34
  rescue Errno::ENOENT
47
35
  raise InvalidCache
@@ -65,17 +53,48 @@ class Money
65
53
 
66
54
  protected
67
55
 
68
- def find_rates_source
69
- if !!cache && File.exist?(cache)
70
- @rates_source = cache
56
+ # Store the provided text data by calling the proc method provided
57
+ # for the cache, or write to the cache file.
58
+ def store_in_cache(text)
59
+ if cache.is_a?(Proc)
60
+ cache.call(text)
61
+ elsif cache.is_a?(String)
62
+ open(cache, 'w') do |f|
63
+ f.write(text)
64
+ end
65
+ else
66
+ nil
67
+ end
68
+ end
69
+
70
+ def read_from_cache
71
+ if cache.is_a?(Proc)
72
+ cache.call(nil)
73
+ elsif cache.is_a?(String) && File.exist?(cache)
74
+ open(cache).read
71
75
  else
72
- OER_URL
76
+ nil
73
77
  end
74
78
  end
75
79
 
80
+ def source_url
81
+ raise NoAppId if app_id.nil? || app_id.empty?
82
+ "#{OER_URL}?app_id=#{app_id}"
83
+ end
84
+
85
+ def read_from_url
86
+ open(source_url).read
87
+ end
88
+
89
+ def has_valid_rates?(text)
90
+ parsed = MultiJson.decode(text)
91
+ parsed && parsed.has_key?('rates')
92
+ rescue MultiJson::DecodeError
93
+ false
94
+ end
95
+
76
96
  def exchange_rates
77
- @rates_source = find_rates_source
78
- @doc = Yajl::Parser.parse(open(rates_source).read)
97
+ @doc = MultiJson.decode(read_from_cache || read_from_url)
79
98
  @oer_rates = @doc['rates']
80
99
  @doc['rates']
81
100
  end
@@ -4,11 +4,16 @@ require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper'))
4
4
 
5
5
  describe Money::Bank::OpenExchangeRatesBank do
6
6
 
7
+ before do
8
+ @cache_path = File.expand_path(File.join(File.dirname(__FILE__), 'latest.json'))
9
+ end
10
+
7
11
  describe 'exchange' do
8
12
  include RR::Adapters::TestUnit
9
13
 
10
14
  before do
11
15
  @bank = Money::Bank::OpenExchangeRatesBank.new
16
+ @bank.app_id = TEST_APP_ID
12
17
  @temp_cache_path = File.expand_path(File.join(File.dirname(__FILE__), 'tmp.json'))
13
18
  @bank.cache = @temp_cache_path
14
19
  stub(OpenURI::OpenRead).open(Money::Bank::OpenExchangeRatesBank::OER_URL) { File.read @cache_path }
@@ -16,20 +21,20 @@ describe Money::Bank::OpenExchangeRatesBank do
16
21
  end
17
22
 
18
23
  it "should be able to exchange a money to its own currency even without rates" do
19
- money = Money.new(0, "USD");
24
+ money = Money.new(0, "USD")
20
25
  @bank.exchange_with(money, "USD").must_equal money
21
26
  end
22
27
 
23
28
  it "should raise if it can't find an exchange rate" do
24
- money = Money.new(0, "USD");
29
+ money = Money.new(0, "USD")
25
30
  assert_raises(Money::Bank::UnknownRateFormat){ @bank.exchange_with(money, "AUD") }
26
31
  end
27
32
  end
28
33
 
29
34
  describe 'update_rates' do
30
35
  before do
31
- @cache_path = File.expand_path(File.join(File.dirname(__FILE__), 'latest.json'))
32
36
  @bank = Money::Bank::OpenExchangeRatesBank.new
37
+ @bank.app_id = TEST_APP_ID
33
38
  @bank.cache = @cache_path
34
39
  @bank.update_rates
35
40
  end
@@ -45,7 +50,8 @@ describe Money::Bank::OpenExchangeRatesBank do
45
50
  @bank.oer_rates.keys.each do |currency|
46
51
  next unless Money::Currency.find(currency)
47
52
  subunit = Money::Currency.wrap(currency).subunit_to_unit
48
- @bank.exchange(100, "USD", currency).cents.must_equal((@bank.oer_rates[currency].to_f * subunit).round)
53
+ @bank.exchange(100, "USD", currency).cents.
54
+ must_equal((@bank.oer_rates[currency].to_f * subunit).round)
49
55
  end
50
56
  end
51
57
 
@@ -53,8 +59,11 @@ describe Money::Bank::OpenExchangeRatesBank do
53
59
  @bank.oer_rates.keys.each do |currency|
54
60
  next unless Money::Currency.find(currency)
55
61
  subunit = Money::Currency.wrap(currency).subunit_to_unit
56
- @bank.exchange_with(Money.new(100, "USD"), currency).cents.must_equal((@bank.oer_rates[currency].to_f * subunit).round)
57
- @bank.exchange_with(1.to_money("USD"), currency).cents.must_equal((@bank.oer_rates[currency].to_f * subunit).round)
62
+ @bank.exchange_with(Money.new(100, "USD"), currency).cents.
63
+ must_equal((@bank.oer_rates[currency].to_f * subunit).round)
64
+
65
+ @bank.exchange_with(1.to_money("USD"), currency).cents.
66
+ must_equal((@bank.oer_rates[currency].to_f * subunit).round)
58
67
  end
59
68
  @bank.exchange_with(5000.to_money('JPY'), 'USD').cents.must_equal 6441
60
69
  end
@@ -94,18 +103,38 @@ describe Money::Bank::OpenExchangeRatesBank do
94
103
  end
95
104
  end
96
105
 
106
+ describe 'App ID' do
107
+ include RR::Adapters::TestUnit
108
+
109
+ before do
110
+ @bank = Money::Bank::OpenExchangeRatesBank.new
111
+ @temp_cache_path = File.expand_path(File.join(File.dirname(__FILE__), 'tmp.json'))
112
+ @bank.cache = @temp_cache_path
113
+ stub(OpenURI::OpenRead).open(Money::Bank::OpenExchangeRatesBank::OER_URL) { File.read @cache_path }
114
+ end
115
+
116
+ it 'should raise an error if no App ID is set' do
117
+ proc {@bank.save_rates}.must_raise Money::Bank::NoAppId
118
+ end
119
+
120
+ #TODO: As App IDs are compulsory soon, need to add more tests handle
121
+ # app_id-specific errors from
122
+ # https://openexchangerates.org/documentation#errors
123
+ end
124
+
97
125
  describe 'no cache' do
98
126
  include RR::Adapters::TestUnit
99
127
 
100
128
  before do
101
129
  @bank = Money::Bank::OpenExchangeRatesBank.new
102
130
  @bank.cache = nil
131
+ @bank.app_id = TEST_APP_ID
103
132
  end
104
133
 
105
134
  it 'should get from url' do
106
135
  stub(OpenURI::OpenRead).open(Money::Bank::OpenExchangeRatesBank::OER_URL) { File.read @cache_path }
107
136
  @bank.update_rates
108
- @bank.rates_source.must_equal Money::Bank::OpenExchangeRatesBank::OER_URL
137
+ @bank.oer_rates.wont_be_empty
109
138
  end
110
139
 
111
140
  it 'should raise an error if invalid path is given to save_rates' do
@@ -118,12 +147,13 @@ describe Money::Bank::OpenExchangeRatesBank do
118
147
  before do
119
148
  @bank = Money::Bank::OpenExchangeRatesBank.new
120
149
  @bank.cache = "space_dir#{rand(999999999)}/out_space_file.json"
150
+ @bank.app_id = TEST_APP_ID
121
151
  end
122
152
 
123
153
  it 'should get from url' do
124
154
  stub(OpenURI::OpenRead).open(Money::Bank::OpenExchangeRatesBank::OER_URL) { File.read @cache_path }
125
155
  @bank.update_rates
126
- @bank.rates_source.must_equal Money::Bank::OpenExchangeRatesBank::OER_URL
156
+ @bank.oer_rates.wont_be_empty
127
157
  end
128
158
 
129
159
  it 'should raise an error if invalid path is given to save_rates' do
@@ -131,11 +161,45 @@ describe Money::Bank::OpenExchangeRatesBank do
131
161
  end
132
162
  end
133
163
 
164
+ describe 'using proc for cache' do
165
+ include RR::Adapters::TestUnit
166
+
167
+ before :each do
168
+ $global_rates = nil
169
+ @bank = Money::Bank::OpenExchangeRatesBank.new
170
+ @bank.cache = Proc.new {|v|
171
+ if v
172
+ $global_rates = v
173
+ else
174
+ $global_rates
175
+ end
176
+ }
177
+ @bank.app_id = TEST_APP_ID
178
+ end
179
+
180
+ it 'should get from url normally' do
181
+ stub(@bank).source_url() { @cache_path }
182
+ @bank.update_rates
183
+ @bank.oer_rates.wont_be_empty
184
+ end
185
+
186
+ it 'should save from url and get from cache' do
187
+ stub(@bank).source_url { @cache_path }
188
+ @bank.save_rates
189
+ $global_rates.wont_be_empty
190
+ dont_allow(@bank).source_url
191
+ @bank.update_rates
192
+ @bank.oer_rates.wont_be_empty
193
+ end
194
+
195
+ end
196
+
134
197
  describe 'save rates' do
135
198
  include RR::Adapters::TestUnit
136
199
 
137
200
  before do
138
201
  @bank = Money::Bank::OpenExchangeRatesBank.new
202
+ @bank.app_id = "temp-e091fc14b3884a516d6cc2c299a"
139
203
  @temp_cache_path = File.expand_path(File.join(File.dirname(__FILE__), 'tmp.json'))
140
204
  @bank.cache = @temp_cache_path
141
205
  stub(OpenURI::OpenRead).open(Money::Bank::OpenExchangeRatesBank::OER_URL) { File.read @cache_path }
@@ -2,3 +2,10 @@
2
2
  require 'minitest/autorun'
3
3
  require 'rr'
4
4
  require 'money/bank/open_exchange_rates_bank'
5
+
6
+ TEST_APP_ID_PATH = File.join(File.dirname(__FILE__), '..', 'TEST_APP_ID')
7
+ TEST_APP_ID = ENV['TEST_APP_ID'] || File.read(TEST_APP_ID_PATH)
8
+
9
+ if TEST_APP_ID.nil? || TEST_APP_ID.empty?
10
+ raise "Please add a valid app id to file #{TEST_APP_ID_PATH} or to TEST_APP_ID environment"
11
+ end
metadata CHANGED
@@ -1,30 +1,31 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: money-open-exchange-rates
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.7
4
+ version: 0.1.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
8
8
  - Laurent Arnoud
9
+ - Sam Lown
9
10
  autorequire:
10
11
  bindir: bin
11
12
  cert_chain: []
12
- date: 2012-07-23 00:00:00.000000000 Z
13
+ date: 2012-09-30 00:00:00.000000000 Z
13
14
  dependencies:
14
15
  - !ruby/object:Gem::Dependency
15
- name: yajl-ruby
16
- requirement: &20001460 !ruby/object:Gem::Requirement
16
+ name: multi_json
17
+ requirement: &17136700 !ruby/object:Gem::Requirement
17
18
  none: false
18
19
  requirements:
19
- - - ! '>='
20
+ - - ~>
20
21
  - !ruby/object:Gem::Version
21
- version: 0.8.3
22
+ version: '1.0'
22
23
  type: :runtime
23
24
  prerelease: false
24
- version_requirements: *20001460
25
+ version_requirements: *17136700
25
26
  - !ruby/object:Gem::Dependency
26
27
  name: money
27
- requirement: &20000360 !ruby/object:Gem::Requirement
28
+ requirement: &17136000 !ruby/object:Gem::Requirement
28
29
  none: false
29
30
  requirements:
30
31
  - - ! '>='
@@ -32,10 +33,10 @@ dependencies:
32
33
  version: 3.7.1
33
34
  type: :runtime
34
35
  prerelease: false
35
- version_requirements: *20000360
36
+ version_requirements: *17136000
36
37
  - !ruby/object:Gem::Dependency
37
38
  name: minitest
38
- requirement: &19999680 !ruby/object:Gem::Requirement
39
+ requirement: &17135280 !ruby/object:Gem::Requirement
39
40
  none: false
40
41
  requirements:
41
42
  - - ! '>='
@@ -43,10 +44,10 @@ dependencies:
43
44
  version: '2.0'
44
45
  type: :development
45
46
  prerelease: false
46
- version_requirements: *19999680
47
+ version_requirements: *17135280
47
48
  - !ruby/object:Gem::Dependency
48
49
  name: rr
49
- requirement: &19999220 !ruby/object:Gem::Requirement
50
+ requirement: &17134700 !ruby/object:Gem::Requirement
50
51
  none: false
51
52
  requirements:
52
53
  - - ! '>='
@@ -54,7 +55,7 @@ dependencies:
54
55
  version: 1.0.4
55
56
  type: :development
56
57
  prerelease: false
57
- version_requirements: *19999220
58
+ version_requirements: *17134700
58
59
  description: A gem that calculates the exchange rate using published rates from open-exchange-rates.
59
60
  Compatible with the money gem.
60
61
  email: laurent@spkdev.net