currency 0.3.3 → 0.4.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.
Files changed (54) hide show
  1. data/COPYING.txt +339 -0
  2. data/LICENSE.txt +62 -0
  3. data/Manifest.txt +37 -14
  4. data/README.txt +8 -0
  5. data/Rakefile +42 -8
  6. data/Releases.txt +26 -0
  7. data/TODO.txt +1 -0
  8. data/examples/ex1.rb +3 -3
  9. data/examples/xe1.rb +3 -2
  10. data/lib/currency.rb +71 -9
  11. data/lib/currency/active_record.rb +138 -21
  12. data/lib/currency/core_extensions.rb +7 -5
  13. data/lib/currency/currency.rb +94 -177
  14. data/lib/currency/{currency_factory.rb → currency/factory.rb} +46 -25
  15. data/lib/currency/currency_version.rb +3 -3
  16. data/lib/currency/exception.rb +14 -14
  17. data/lib/currency/exchange.rb +14 -12
  18. data/lib/currency/exchange/rate.rb +159 -28
  19. data/lib/currency/exchange/rate/deriver.rb +146 -0
  20. data/lib/currency/exchange/rate/source.rb +84 -0
  21. data/lib/currency/exchange/rate/source/base.rb +156 -0
  22. data/lib/currency/exchange/rate/source/failover.rb +57 -0
  23. data/lib/currency/exchange/rate/source/historical.rb +79 -0
  24. data/lib/currency/exchange/rate/source/historical/rate.rb +181 -0
  25. data/lib/currency/exchange/rate/source/historical/writer.rb +203 -0
  26. data/lib/currency/exchange/rate/source/new_york_fed.rb +91 -0
  27. data/lib/currency/exchange/rate/source/provider.rb +105 -0
  28. data/lib/currency/exchange/rate/source/test.rb +50 -0
  29. data/lib/currency/exchange/rate/source/the_financials.rb +190 -0
  30. data/lib/currency/exchange/rate/source/timed_cache.rb +144 -0
  31. data/lib/currency/exchange/rate/source/xe.rb +166 -0
  32. data/lib/currency/exchange/time_quantitizer.rb +111 -0
  33. data/lib/currency/formatter.rb +159 -0
  34. data/lib/currency/macro.rb +321 -0
  35. data/lib/currency/money.rb +90 -64
  36. data/lib/currency/money_helper.rb +6 -5
  37. data/lib/currency/parser.rb +153 -0
  38. data/test/ar_column_test.rb +6 -3
  39. data/test/ar_simple_test.rb +5 -2
  40. data/test/ar_test_base.rb +39 -33
  41. data/test/ar_test_core.rb +64 -0
  42. data/test/formatter_test.rb +81 -0
  43. data/test/historical_writer_test.rb +184 -0
  44. data/test/macro_test.rb +109 -0
  45. data/test/money_test.rb +72 -4
  46. data/test/new_york_fed_test.rb +57 -0
  47. data/test/parser_test.rb +60 -0
  48. data/test/test_base.rb +13 -3
  49. data/test/time_quantitizer_test.rb +136 -0
  50. data/test/xe_test.rb +29 -5
  51. metadata +41 -18
  52. data/lib/currency/exchange/base.rb +0 -84
  53. data/lib/currency/exchange/test.rb +0 -39
  54. data/lib/currency/exchange/xe.rb +0 -250
@@ -1,29 +1,53 @@
1
+ # Copyright (C) 2006-2007 Kurt Stephens <ruby-currency(at)umleta.com>
2
+ # See LICENSE.txt for details.
3
+
1
4
  require 'test/test_base'
5
+
2
6
  require 'currency' # For :type => :money
3
- require 'currency/exchange/xe'
7
+ require 'currency/exchange/rate/source/xe'
4
8
 
5
9
  module Currency
6
10
 
7
11
  class XeTest < TestBase
8
12
  def setup
9
13
  super
10
- # Force XE Exchange.
11
- Exchange.default = Exchange::Xe.instance
12
14
  end
13
15
 
16
+
17
+ def get_rate_source
18
+ source = Exchange::Rate::Source::Xe.new
19
+ deriver = Exchange::Rate::Deriver.new(:source => source)
20
+ end
21
+
22
+
14
23
  def test_xe_usd_cad
15
- assert_not_nil rates = Exchange.default.xe_rates
24
+ assert_not_nil rates = Exchange::Rate::Source.default.source.raw_rates
16
25
  assert_not_nil rates[:USD]
17
26
  assert_not_nil usd_cad = rates[:USD][:CAD]
18
27
 
19
28
  assert_not_nil usd = Money.new(123.45, :USD)
20
29
  assert_not_nil cad = usd.convert(:CAD)
21
30
 
22
- assert_kind_of Numeric, m = (cad.rep.to_f / usd.rep.to_f)
31
+ assert_kind_of Numeric, m = (cad.to_f / usd.to_f)
23
32
  # $stderr.puts "m = #{m}"
24
33
  assert_equal_float usd_cad, m, 0.001
25
34
  end
26
35
 
36
+
37
+ def test_xe_cad_eur
38
+ assert_not_nil rates = Exchange::Rate::Source.default.source.raw_rates
39
+ assert_not_nil rates[:USD]
40
+ assert_not_nil usd_cad = rates[:USD][:CAD]
41
+ assert_not_nil usd_eur = rates[:USD][:EUR]
42
+
43
+ assert_not_nil cad = Money.new(123.45, :CAD)
44
+ assert_not_nil eur = cad.convert(:EUR)
45
+
46
+ assert_kind_of Numeric, m = (eur.to_f / cad.to_f)
47
+ # $stderr.puts "m = #{m}"
48
+ assert_equal_float (1.0 / usd_cad) * usd_eur, m, 0.001
49
+ end
50
+
27
51
  end
28
52
 
29
53
  end # module
metadata CHANGED
@@ -3,16 +3,16 @@ rubygems_version: 0.9.0
3
3
  specification_version: 1
4
4
  name: currency
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.3.3
7
- date: 2006-10-31 00:00:00 -05:00
8
- summary: Currency models currencies, monetary values, foreign exchanges and rates. Supports ActiveRecord. See http://currency.rubyforge.org/files/lib/currency_rb.html for more details.
6
+ version: 0.4.0
7
+ date: 2007-02-21 00:00:00 -05:00
8
+ summary: "Currency models currencies, monetary values, foreign exchanges and rates. Pulls live rates from http://xe.com/. Supports ActiveRecord. For more details, see: http://currency.rubyforge.org/files/lib/currency_rb.html http://currency.rubyforge.org/files/README.txt http://currency.rubyforge.org/"
9
9
  require_paths:
10
10
  - lib
11
11
  - test
12
12
  email: ruby-currency@umleta.com
13
13
  homepage: http://rubyforge.org/projects/currency
14
14
  rubyforge_project: currency
15
- description: Currency models currencies, monetary values, foreign exchanges and rates. Supports ActiveRecord. See http://currency.rubyforge.org/files/lib/currency_rb.html for more details.
15
+ description: "Currency models currencies, monetary values, foreign exchanges and rates. Pulls live rates from http://xe.com/. Supports ActiveRecord. For more details, see: http://currency.rubyforge.org/files/lib/currency_rb.html http://currency.rubyforge.org/files/README.txt http://currency.rubyforge.org/"
16
16
  autorequire:
17
17
  default_executable:
18
18
  bindir: bin
@@ -30,34 +30,57 @@ post_install_message:
30
30
  authors:
31
31
  - Kurt Stephens
32
32
  files:
33
+ - COPYING.txt
33
34
  - ChangeLog
35
+ - LICENSE.txt
36
+ - Manifest.txt
37
+ - README.txt
38
+ - Rakefile
39
+ - Releases.txt
40
+ - TODO.txt
34
41
  - examples/ex1.rb
35
42
  - examples/xe1.rb
43
+ - lib/currency.rb
36
44
  - lib/currency/active_record.rb
37
45
  - lib/currency/core_extensions.rb
38
- - lib/currency/currency_factory.rb
39
46
  - lib/currency/currency.rb
47
+ - lib/currency/currency/factory.rb
40
48
  - lib/currency/currency_version.rb
41
49
  - lib/currency/exception.rb
42
- - lib/currency/exchange/base.rb
43
- - lib/currency/exchange/rate.rb
44
50
  - lib/currency/exchange.rb
45
- - lib/currency/exchange/test.rb
46
- - lib/currency/exchange/xe.rb
47
- - lib/currency/money_helper.rb
51
+ - lib/currency/exchange/rate.rb
52
+ - lib/currency/exchange/rate/deriver.rb
53
+ - lib/currency/exchange/rate/source.rb
54
+ - lib/currency/exchange/rate/source/base.rb
55
+ - lib/currency/exchange/rate/source/failover.rb
56
+ - lib/currency/exchange/rate/source/historical.rb
57
+ - lib/currency/exchange/rate/source/historical/rate.rb
58
+ - lib/currency/exchange/rate/source/historical/writer.rb
59
+ - lib/currency/exchange/rate/source/new_york_fed.rb
60
+ - lib/currency/exchange/rate/source/provider.rb
61
+ - lib/currency/exchange/rate/source/test.rb
62
+ - lib/currency/exchange/rate/source/the_financials.rb
63
+ - lib/currency/exchange/rate/source/timed_cache.rb
64
+ - lib/currency/exchange/rate/source/xe.rb
65
+ - lib/currency/exchange/time_quantitizer.rb
66
+ - lib/currency/formatter.rb
67
+ - lib/currency/macro.rb
48
68
  - lib/currency/money.rb
49
- - lib/currency.rb
50
- - Manifest.txt
51
- - Rakefile
52
- - README.txt
53
- - Releases.txt
54
- - test/ar_test_base.rb
55
- - test/ar_simple_test.rb
69
+ - lib/currency/money_helper.rb
70
+ - lib/currency/parser.rb
56
71
  - test/ar_column_test.rb
72
+ - test/ar_simple_test.rb
73
+ - test/ar_test_base.rb
74
+ - test/ar_test_core.rb
75
+ - test/formatter_test.rb
76
+ - test/historical_writer_test.rb
77
+ - test/macro_test.rb
57
78
  - test/money_test.rb
79
+ - test/new_york_fed_test.rb
80
+ - test/parser_test.rb
58
81
  - test/test_base.rb
82
+ - test/time_quantitizer_test.rb
59
83
  - test/xe_test.rb
60
- - TODO.txt
61
84
  test_files: []
62
85
 
63
86
  rdoc_options: []
@@ -1,84 +0,0 @@
1
- # -*- ruby -*-
2
- #
3
- # = Currency::Exchange::Base
4
- #
5
- # The Currency::Exchange::Base class is the base class for
6
- # currency exchange rate providers.
7
- #
8
- # Currency::Exchange::Base subclasses are Currency::Exchange::Rate
9
- # factories.
10
- #
11
-
12
- module Currency
13
- module Exchange
14
-
15
- # Represents a method of converting between two currencies
16
- class Base
17
-
18
- # The name of this Exchange.
19
- attr_accessor :name
20
-
21
- # If true, this Exchange will log information.
22
- attr_accessor :verbose
23
-
24
- def initialize(*opt)
25
- @name = nil
26
- @rate = { }
27
- opt = Hash[*opt]
28
- opt.each{|k,v| self.send(k, v)}
29
- end
30
-
31
- # Converts Money m in Currency c1 to a new
32
- # Money value in Currency c2.
33
- def convert(m, c2, c1 = nil)
34
- c1 = m.currency if c1 == nil
35
- if ( c1 == c2 )
36
- m
37
- else
38
- Money.new(rate(c1, c2).convert(m, c1), c2)
39
- end
40
- end
41
-
42
- # Flush all cached Rate.
43
- def clear_rates
44
- @rate.clear
45
- end
46
-
47
- # Flush any cached Rate between Currency c1 and c2.
48
- def clear_rate(c1, c2, recip = true)
49
- @rate[c1.code.to_s + c2.code.to_s] = nil
50
- @rate[c2.code.to_s + c1.code.to_s] = nil if recip
51
- end
52
-
53
- # Returns the Rate between Currency c1 and c2.
54
- #
55
- # This will call #get_rate(c1, c2) if the
56
- # Rate has not already been cached.
57
- #
58
- # Subclasses can override this method to implement
59
- # rate expiration rules.
60
- #
61
- def rate(c1, c2)
62
- (@rate[c1.code.to_s + c2.code.to_s] ||= get_rate(c1, c2)) ||
63
- (@rate[c2.code.to_s + c1.code.to_s] ||= get_rate(c2, c1))
64
- end
65
-
66
-
67
- # Determines and creates the Rate between Currency c1 and c2.
68
- #
69
- # Subclasses are required to implement this method.
70
- def get_rate(c1, c2)
71
- raise Exception::UnknownRate.new("Subclass responsibility: get_rate")
72
- end
73
-
74
- # Returns a simple string rep of an Exchange object.
75
- def to_s
76
- "#<#{self.class.name} #{self.name && self.name.inspect}>"
77
- end
78
-
79
- end # class
80
-
81
-
82
- end # module
83
- end # module
84
-
@@ -1,39 +0,0 @@
1
- # This class is a test Exchange.
2
- # It can convert only between USD and CAD.
3
-
4
- module Currency
5
- module Exchange
6
-
7
- class Test < Base
8
- @@instance = nil
9
-
10
- # Returns a singleton instance.
11
- def self.instance(*opts)
12
- @@instance ||= self.new(*opts)
13
- end
14
-
15
- def initialize(*opts)
16
- super(*opts)
17
- end
18
-
19
- # Test rate from :USD to :CAD.
20
- def self.USD_CAD; 1.1708; end
21
-
22
- # Returns test Rate for USD and CAD pairs.
23
- def get_rate(c1, c2)
24
- # $stderr.puts "load_exchange_rate(#{c1}, #{c2})"
25
- rate = 0.0
26
- if ( c1.code == :USD && c2.code == :CAD )
27
- rate = self.class.USD_CAD
28
- end
29
- rate > 0 ? Rate.new(c1, c2, rate, self) : nil
30
- end
31
-
32
- end # class
33
-
34
- end # module
35
- end # module
36
-
37
- # Install as default.
38
- Currency::Exchange.default = Currency::Exchange::Test.instance
39
-
@@ -1,250 +0,0 @@
1
- # Connects to http://xe.com and parses "XE.com Quick Cross Rates"
2
- # from home page HTML.
3
-
4
- require 'net/http'
5
- require 'open-uri'
6
-
7
- module Currency
8
- module Exchange
9
-
10
- class Xe < Base
11
- @@instance = nil
12
- # Returns a singleton instance.
13
- def self.instance(*opts)
14
- @@instance ||= self.new(*opts)
15
- end
16
-
17
- # Defaults to "http://xe.com/"
18
- attr_accessor :uri
19
-
20
- # Defines the number of seconds rates until rates
21
- # become invalid, causing a request of new rates.
22
- #
23
- # Defaults to 600 seconds.
24
- attr_accessor :time_to_live
25
-
26
- # Defines the number of random seconds to add before
27
- # rates become invalid.
28
- #
29
- # Defaults to 30 seconds.
30
- attr_accessor :time_to_live_fudge
31
-
32
- # This Exchange's name is the same as its #uri.
33
- def name
34
- uri
35
- end
36
-
37
- def initialize(*opt)
38
- self.uri = 'http://xe.com/'
39
- self.time_to_live = 600
40
- self.time_to_live_fudge = 30
41
- @xe_rates = nil
42
- super(*opt)
43
- end
44
-
45
- def clear_rates
46
- @xe_rates && @xe_rates.clear
47
- super
48
- end
49
-
50
- def expired?
51
- if @time_to_live &&
52
- @xe_rates_renew_time &&
53
- (Time.now > @xe_rates_renew_time)
54
-
55
- if @xe_rates
56
- $stderr.puts "#{self}: rates expired on #{@xe_rates_renew_time}" if @verbose
57
-
58
- @old_rates ||= @xe_rates
59
-
60
- @xe_rates = nil
61
- end
62
-
63
- true
64
- else
65
- false
66
- end
67
- end
68
-
69
- # Check expired? before returning a Rate.
70
- def rate(c1, c2)
71
- if expired?
72
- clear_rates
73
- end
74
- super(c1, c2)
75
- end
76
-
77
- # Returns a cached Hash of rates:
78
- #
79
- # xe.xe_rates[:USD][:CAD] => 1.0134
80
- #
81
- def xe_rates
82
- old_rates = nil
83
- # Check expiration.
84
- expired?
85
-
86
- # Return rates, if cached.
87
- return @xe_rates if @xe_rates
88
-
89
- # Force load of rates
90
- @xe_rates = xe_rates_load
91
-
92
- # Flush old rates.
93
- @old_rates = nil
94
-
95
- # Update expiration.
96
- if time_to_live
97
- @xe_rates_renew_time = @rate_timestamp + (time_to_live + (time_to_live_fudge || 0))
98
- $stderr.puts "#{self}: rates expire on #{@xe_rates_renew_time}" if @verbose
99
- end
100
-
101
- @xe_rates
102
- end
103
-
104
- def xe_rates_load
105
- # Do not allow re-entrancy
106
- raise "Reentrant!" if @processing_rates
107
-
108
- # Begin processing new rate request.
109
- @processing_rates = true
110
-
111
- # Clear cached Rates.
112
- clear_rates
113
-
114
- # Parse rates from HTML page.
115
- rates = parse_page_rates
116
-
117
- unless rates
118
- # FIXME: raise Exception::???
119
- return rates
120
- end
121
-
122
- # Compute new rate timeps
123
- @rate_timestamp = Time.now # TODO: Extract this from HTML page!
124
-
125
- # End processsing new rate request.
126
- @processing_rates = false
127
-
128
- rates
129
- end
130
-
131
-
132
- # Returns the URI content.
133
- def get_page
134
- data = open(uri) { |data| data.read }
135
-
136
- data = data.split(/[\r\n]/)
137
-
138
- data
139
- end
140
-
141
- # Parses http://xe.com homepage HTML for
142
- # quick rates of 10 currencies.
143
- def parse_page_rates(data = nil)
144
- data = get_page unless data
145
-
146
- # Chomp after
147
- until data.empty?
148
- line = data.pop
149
- break if line =~ /Need More currencies\?/
150
- end
151
-
152
- # Chomp before
153
- until data.empty?
154
- line = data.shift
155
- break if line =~ /XE.com Quick Cross Rates/
156
- end
157
-
158
- until data.empty?
159
- line = data.shift
160
- break if line =~ /Confused about how to use the rates/i
161
- end
162
-
163
- until data.empty?
164
- line = data.shift
165
- break if line =~ /^\s*<\/tr>/i
166
- end
167
- # $stderr.puts "#{data[0..4].inspect}"
168
-
169
- # Read first table row to get position for each currency
170
- currency = [ ]
171
- until data.empty?
172
- line = data.shift
173
- break if line =~ /^\s*<\/tr>/i
174
- if md = /<td><IMG .+ ALT="([A-Z][A-Z][A-Z])"/i.match(line) #"
175
- cur = md[1].intern
176
- cur_i = currency.size
177
- currency.push(cur)
178
- # $stderr.puts "Found currency header: #{cur.inspect} at #{cur_i}"
179
- end
180
- end
181
-
182
- # $stderr.puts "#{data[0..4].inspect}"
183
-
184
- # Skip blank <tr>
185
- until data.empty?
186
- line = data.shift
187
- break if line =~ /^\s*<td>.+1.+USD.+=/
188
- end
189
-
190
- until data.empty?
191
- line = data.shift
192
- break if line =~ /^\s*<\/tr>/i
193
- end
194
-
195
- # $stderr.puts "#{data[0..4].inspect}"
196
-
197
- # Read first row of 1 USD = ...
198
-
199
- rate = { }
200
- cur_i = -1
201
- until data.empty?
202
- line = data.shift
203
- break if cur_i < 0 && line =~ /^\s*<\/tr>/i
204
- if md = /<td>\s+(\d+\.\d+)\s+<\/td>/.match(line)
205
- usd_to_cur = md[1].to_f
206
- cur_i = cur_i + 1
207
- cur = currency[cur_i]
208
- (rate[:USD] ||= {})[cur] = usd_to_cur
209
- end
210
- end
211
-
212
- rate
213
- end
214
-
215
- # Loads cached rates from xe.com and creates Rate objects
216
- # for 10 currencies.
217
- def get_rate(c1, c2)
218
- rates = xe_rates # Load rates
219
-
220
- # $stderr.puts "load_exchange_rate(#{c1}, #{c2})"
221
- rate = 0.0
222
- r1 = nil
223
- r2 = nil
224
-
225
- rates_usd = rates[:USD]
226
-
227
- raise Exception::UnknownRate.new("#{self}: base rate :USD") unless rates_usd
228
-
229
- if ( c1.code == :USD && (r2 = rates_usd[c2.code]) )
230
- rate = r2
231
- elsif ( c2.code == :USD && (r1 = rates_usd[c2.code]) )
232
- rate = 1.0 / r1
233
- elsif ( (r1 = rates_usd[c1.code]) && (r2 = rates_usd[c2.code]) )
234
- rate = r2 / r1
235
- end
236
-
237
- # $stderr.puts "XE Rate: #{c1.code} / #{c2.code} = #{rate}"
238
-
239
- rate > 0 ? Rate.new(c1, c2, rate, self, @rate_timestamp) : nil
240
- end
241
-
242
- end # class
243
-
244
- end # module
245
- end # module
246
-
247
-
248
- # Install as default.
249
- Currency::Exchange.default = Currency::Exchange::Xe.instance
250
-