mumboe-currency 0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. data/bin/currency_historical_rate_load +105 -0
  2. data/examples/ex1.rb +13 -0
  3. data/examples/xe1.rb +20 -0
  4. data/lib/currency/active_record.rb +265 -0
  5. data/lib/currency/config.rb +91 -0
  6. data/lib/currency/core_extensions.rb +41 -0
  7. data/lib/currency/currency/factory.rb +228 -0
  8. data/lib/currency/currency.rb +175 -0
  9. data/lib/currency/currency_version.rb +6 -0
  10. data/lib/currency/exception.rb +119 -0
  11. data/lib/currency/exchange/rate/deriver.rb +157 -0
  12. data/lib/currency/exchange/rate/source/base.rb +166 -0
  13. data/lib/currency/exchange/rate/source/failover.rb +63 -0
  14. data/lib/currency/exchange/rate/source/federal_reserve.rb +160 -0
  15. data/lib/currency/exchange/rate/source/historical/rate.rb +184 -0
  16. data/lib/currency/exchange/rate/source/historical/rate_loader.rb +186 -0
  17. data/lib/currency/exchange/rate/source/historical/writer.rb +220 -0
  18. data/lib/currency/exchange/rate/source/historical.rb +79 -0
  19. data/lib/currency/exchange/rate/source/new_york_fed.rb +127 -0
  20. data/lib/currency/exchange/rate/source/provider.rb +120 -0
  21. data/lib/currency/exchange/rate/source/test.rb +50 -0
  22. data/lib/currency/exchange/rate/source/the_financials.rb +191 -0
  23. data/lib/currency/exchange/rate/source/timed_cache.rb +198 -0
  24. data/lib/currency/exchange/rate/source/xe.rb +165 -0
  25. data/lib/currency/exchange/rate/source.rb +89 -0
  26. data/lib/currency/exchange/rate.rb +214 -0
  27. data/lib/currency/exchange/time_quantitizer.rb +111 -0
  28. data/lib/currency/exchange.rb +50 -0
  29. data/lib/currency/formatter.rb +290 -0
  30. data/lib/currency/macro.rb +321 -0
  31. data/lib/currency/money.rb +295 -0
  32. data/lib/currency/money_helper.rb +13 -0
  33. data/lib/currency/parser.rb +151 -0
  34. data/lib/currency.rb +143 -0
  35. data/test/string_test.rb +54 -0
  36. data/test/test_base.rb +44 -0
  37. metadata +90 -0
@@ -0,0 +1,228 @@
1
+ # Copyright (C) 2006-2007 Kurt Stephens <ruby-currency(at)umleta.com>
2
+ # See LICENSE.txt for details.
3
+
4
+
5
+ # Responsible for creating Currency::Currency objects on-demand.
6
+ class Currency::Currency::Factory
7
+ @@default = nil
8
+
9
+ # Returns the default Currency::Factory.
10
+ def self.default
11
+ @@default ||= self.new
12
+ end
13
+ # Sets the default Currency::Factory.
14
+ def self.default=(x)
15
+ @@default = x
16
+ end
17
+
18
+
19
+ def initialize(*opts)
20
+ @currency_by_code = { }
21
+ @currency_by_symbol = { }
22
+ @currency = nil
23
+ end
24
+
25
+
26
+ # Lookup Currency by code.
27
+ def get_by_code(x)
28
+ x = ::Currency::Currency.cast_code(x)
29
+ # $stderr.puts "get_by_code(#{x})"
30
+ @currency_by_code[x] ||= install(load(::Currency::Currency.new(x)))
31
+ end
32
+
33
+
34
+ # Lookup Currency by symbol.
35
+ def get_by_symbol(symbol)
36
+ @currency_by_symbol[symbol] ||= install(load(::Currency::Currency.new(nil, symbol)))
37
+ end
38
+
39
+
40
+ # This method initializes a Currency object as
41
+ # requested from #get_by_code or #get_by_symbol.
42
+ #
43
+ # This method must initialize:
44
+ #
45
+ # currency.code
46
+ # currency.scale
47
+ #
48
+ # Optionally:
49
+ #
50
+ # currency.symbol
51
+ # currency.symbol_html
52
+ #
53
+ # Subclasses that provide Currency metadata should override this method.
54
+ # For example, loading Currency metadata from a database or YAML file.
55
+ def load(currency)
56
+ currency.symbol = CURRENCY_SYMBOLS[currency.code] ? CURRENCY_SYMBOLS[currency.code][:symbol] : nil
57
+ currency.symbol_html = CURRENCY_SYMBOLS[currency.code] ? CURRENCY_SYMBOLS[currency.code][:symbol_html] : nil
58
+ currency.scale = 100
59
+ currency
60
+ end
61
+
62
+ def self.get_currency_from_symbol(symbol)
63
+ return Currency::Currency.get(:USD) if symbol == "$"
64
+ return Currency::Currency.get(:GBP) if symbol == "£"
65
+ return Currency::Currency.get(:EUR) if symbol == "€"
66
+ CURRENCY_SYMBOLS.sort {|a,b| a.to_s <=> b.to_s}.each do |code, hash|
67
+ return Currency::Currency.get(code) if symbol == hash[:symbol]
68
+ end
69
+ Currency::Currency.default
70
+ end
71
+
72
+ CURRENCY_SYMBOLS = {
73
+ :AFN => {:symbol => '؋', :symbol_html => '&#1547;'},
74
+ :ALL => {:symbol => 'Lek', :symbol_html => '&#76;&#101;&#107;'},
75
+ :ANG => {:symbol => 'ƒ', :symbol_html => '&#402;'},
76
+ :ARS => {:symbol => '$', :symbol_html => '&#36;'},
77
+ :AUD => {:symbol => '$', :symbol_html => '&#36;'},
78
+ :AWG => {:symbol => 'ƒ', :symbol_html => '&#402;'},
79
+ :AZN => {:symbol => 'ман', :symbol_html => '&#1084;&#1072;&#1085;'},
80
+ :BAM => {:symbol => 'KM', :symbol_html => '&#75;&#77;'},
81
+ :BBD => {:symbol => '$', :symbol_html => '&#36;'},
82
+ :BGN => {:symbol => 'лв', :symbol_html => '&#1083;&#1074;'},
83
+ :BMD => {:symbol => '$', :symbol_html => '&#36;'},
84
+ :BND => {:symbol => '$', :symbol_html => '&#36;'},
85
+ :BOB => {:symbol => '$b', :symbol_html => '&#36;&#98;'},
86
+ :BRL => {:symbol => 'R$', :symbol_html => '&#82;&#36;'},
87
+ :BSD => {:symbol => '$', :symbol_html => '&#36;'},
88
+ :BWP => {:symbol => 'P', :symbol_html => '&#80;'},
89
+ :BYR => {:symbol => 'p.', :symbol_html => '&#112;&#46;'},
90
+ :BZD => {:symbol => 'BZ$', :symbol_html => '&#66;&#90;&#36;'},
91
+ :CAD => {:symbol => '$', :symbol_html => '&#36;'},
92
+ :CHF => {:symbol => 'CHF', :symbol_html => '&#67;&#72;&#70;'},
93
+ :CLP => {:symbol => '$', :symbol_html => '&#36;'},
94
+ :CNY => {:symbol => '¥', :symbol_html => '&#165;'},
95
+ :COP => {:symbol => '$', :symbol_html => '&#36;'},
96
+ :CRC => {:symbol => '₡', :symbol_html => '&#8353;'},
97
+ :CUP => {:symbol => '₱', :symbol_html => '&#8369;'},
98
+ :CZK => {:symbol => 'Kč', :symbol_html => '&#75;&#269;'},
99
+ :DKK => {:symbol => 'kr', :symbol_html => '&#107;&#114;'},
100
+ :DOP => {:symbol => 'RD$', :symbol_html => '&#82;&#68;&#36;'},
101
+ :EEK => {:symbol => 'kr', :symbol_html => '&#107;&#114;'},
102
+ :EGP => {:symbol => '£', :symbol_html => '&#163;'},
103
+ :EUR => {:symbol => '€', :symbol_html => '&#8364;'},
104
+ :FJD => {:symbol => '$', :symbol_html => '&#36;'},
105
+ :FKP => {:symbol => '£', :symbol_html => '&#163;'},
106
+ :GBP => {:symbol => '£', :symbol_html => '&#163;'},
107
+ :GGP => {:symbol => '£', :symbol_html => '&#163;'},
108
+ :GHC => {:symbol => '¢', :symbol_html => '&#162;'},
109
+ :GIP => {:symbol => '£', :symbol_html => '&#163;'},
110
+ :GTQ => {:symbol => 'Q', :symbol_html => '&#81;'},
111
+ :GYD => {:symbol => '$', :symbol_html => '&#36;'},
112
+ :HKD => {:symbol => '$', :symbol_html => '&#36;'},
113
+ :HNL => {:symbol => 'L', :symbol_html => '&#76;'},
114
+ :HRK => {:symbol => 'kn', :symbol_html => '&#107;&#110;'},
115
+ :HUF => {:symbol => 'Ft', :symbol_html => '&#70;&#116;'},
116
+ :IDR => {:symbol => 'Rp', :symbol_html => '&#82;&#112;'},
117
+ :ILS => {:symbol => '₪', :symbol_html => '&#8362;'},
118
+ :IMP => {:symbol => '£', :symbol_html => '&#163;'},
119
+ :INR => {:symbol => '₨', :symbol_html => '&#8360;'},
120
+ :IRR => {:symbol => '﷼', :symbol_html => '&#65020;'},
121
+ :ISK => {:symbol => 'kr', :symbol_html => '&#107;&#114;'},
122
+ :JEP => {:symbol => '£', :symbol_html => '&#163;'},
123
+ :JMD => {:symbol => 'J$', :symbol_html => '&#74;&#36;'},
124
+ :JPY => {:symbol => '¥', :symbol_html => '&#165;'},
125
+ :KGS => {:symbol => 'лв', :symbol_html => '&#1083;&#1074;'},
126
+ :KHR => {:symbol => '៛', :symbol_html => '&#6107;'},
127
+ :KPW => {:symbol => '₩', :symbol_html => '&#8361;'},
128
+ :KRW => {:symbol => '₩', :symbol_html => '&#8361;'},
129
+ :KYD => {:symbol => '$', :symbol_html => '&#36;'},
130
+ :KZT => {:symbol => 'лв', :symbol_html => '&#1083;&#1074;'},
131
+ :LAK => {:symbol => '₭', :symbol_html => '&#8365;'},
132
+ :LBP => {:symbol => '£', :symbol_html => '&#163;'},
133
+ :LKR => {:symbol => '₨', :symbol_html => '&#8360;'},
134
+ :LRD => {:symbol => '$', :symbol_html => '&#36;'},
135
+ :LTL => {:symbol => 'Lt', :symbol_html => '&#76;&#116;'},
136
+ :LVL => {:symbol => 'Ls', :symbol_html => '&#76;&#115;'},
137
+ :MKD => {:symbol => 'ден', :symbol_html => '&#1076;&#1077;&#1085;'},
138
+ :MNT => {:symbol => '₮', :symbol_html => '&#8366;'},
139
+ :MUR => {:symbol => '₨', :symbol_html => '&#8360;'},
140
+ :MXN => {:symbol => '$', :symbol_html => '&#36;'},
141
+ :MYR => {:symbol => 'RM', :symbol_html => '&#82;&#77;'},
142
+ :MZN => {:symbol => 'MT', :symbol_html => '&#77;&#84;'},
143
+ :NAD => {:symbol => '$', :symbol_html => '&#36;'},
144
+ :NGN => {:symbol => '₦', :symbol_html => '&#8358;'},
145
+ :NIO => {:symbol => 'C$', :symbol_html => '&#67;&#36;'},
146
+ :NOK => {:symbol => 'kr', :symbol_html => '&#107;&#114;'},
147
+ :NPR => {:symbol => '₨', :symbol_html => '&#8360;'},
148
+ :NZD => {:symbol => '$', :symbol_html => '&#36;'},
149
+ :OMR => {:symbol => '﷼', :symbol_html => '&#65020;'},
150
+ :PAB => {:symbol => 'B/.', :symbol_html => '&#66;&#47;&#46;'},
151
+ :PEN => {:symbol => 'S/.', :symbol_html => '&#83;&#47;&#46;'},
152
+ :PHP => {:symbol => 'Php', :symbol_html => '&#80;&#104;&#112;'},
153
+ :PKR => {:symbol => '₨', :symbol_html => '&#8360;'},
154
+ :PLN => {:symbol => 'zł', :symbol_html => '&#122;&#322;'},
155
+ :PYG => {:symbol => 'Gs', :symbol_html => '&#71;&#115;'},
156
+ :QAR => {:symbol => '﷼', :symbol_html => '&#65020;'},
157
+ :RON => {:symbol => 'lei', :symbol_html => '&#108;&#101;&#105;'},
158
+ :RSD => {:symbol => 'Дин.', :symbol_html => '&#1044;&#1080;&#1085;&#46;'},
159
+ :RUB => {:symbol => 'руб', :symbol_html => '&#1088;&#1091;&#1073;'},
160
+ :SAR => {:symbol => '﷼', :symbol_html => '&#65020;'},
161
+ :SBD => {:symbol => '$', :symbol_html => '&#36;'},
162
+ :SCR => {:symbol => '₨', :symbol_html => '&#8360;'},
163
+ :SEK => {:symbol => 'kr', :symbol_html => '&#107;&#114;'},
164
+ :SGD => {:symbol => '$', :symbol_html => '&#36;'},
165
+ :SHP => {:symbol => '£', :symbol_html => '&#163;'},
166
+ :SOS => {:symbol => 'S', :symbol_html => '&#83;'},
167
+ :SRD => {:symbol => '$', :symbol_html => '&#36;'},
168
+ :SVC => {:symbol => '$', :symbol_html => '&#36;'},
169
+ :SYP => {:symbol => '£', :symbol_html => '&#163;'},
170
+ :THB => {:symbol => '฿', :symbol_html => '&#3647;'},
171
+ :TRL => {:symbol => '₤', :symbol_html => '&#8356;'},
172
+ :TRY => {:symbol => 'TL', :symbol_html => '&#84;&#76;'},
173
+ :TTD => {:symbol => 'TT$', :symbol_html => '&#84;&#84;&#36;'},
174
+ :TVD => {:symbol => '$', :symbol_html => '&#36;'},
175
+ :TWD => {:symbol => 'NT$', :symbol_html => '&#78;&#84;&#36;'},
176
+ :UAH => {:symbol => '₴', :symbol_html => '&#8372;'},
177
+ :USD => {:symbol => '$', :symbol_html => '&#36;'},
178
+ :UYU => {:symbol => '$U', :symbol_html => '&#36;&#85;'},
179
+ :UZS => {:symbol => 'лв', :symbol_html => '&#1083;&#1074;'},
180
+ :VEF => {:symbol => 'Bs', :symbol_html => '&#66;&#115;'},
181
+ :VND => {:symbol => '₫', :symbol_html => '&#8363;'},
182
+ :XCD => {:symbol => '$', :symbol_html => '&#36;'},
183
+ :YER => {:symbol => '﷼', :symbol_html => '&#65020;'},
184
+ :ZAR => {:symbol => 'R', :symbol_html => '&#82;'},
185
+ :ZWD => {:symbol => 'Z$', :symbol_html => '&#90;&#36;'},
186
+ }
187
+
188
+ UNIQUE_SYMBOLS = CURRENCY_SYMBOLS.collect {|code, symbol| symbol[:symbol]}.uniq
189
+ CODES = CURRENCY_SYMBOLS.collect {|code, symbol| code}
190
+
191
+ # Installs a new Currency for #get_by_symbol and #get_by_code.
192
+ def install(currency)
193
+ raise ::Currency::Exception::UnknownCurrency unless currency
194
+ @currency_by_symbol[currency.symbol] ||= currency unless currency.symbol.nil?
195
+ @currency_by_code[currency.code] = currency
196
+ end
197
+
198
+
199
+ # Returns the default Currency.
200
+ # Defaults to self.get_by_code(:USD).
201
+ def currency
202
+ @currency ||= self.get_by_code(:USD)
203
+ end
204
+
205
+
206
+ # Sets the default Currency.
207
+ def currency=(x)
208
+ @currency = x
209
+ end
210
+
211
+
212
+ # If selector is [A-Z][A-Z][A-Z], load the currency.
213
+ #
214
+ # factory.USD
215
+ # => #<Currency::Currency:0xb7d0917c @formatter=nil, @scale_exp=2, @scale=100, @symbol="$", @format_left=-3, @code=:USD, @parser=nil, @format_right=-2>
216
+ #
217
+ def method_missing(sel, *args, &blk)
218
+ if args.size == 0 && (! block_given?) && /^[A-Z][A-Z][A-Z]$/.match(sel.to_s)
219
+ self.get_by_code(sel)
220
+ else
221
+ super
222
+ end
223
+ end
224
+
225
+ end # class
226
+
227
+
228
+
@@ -0,0 +1,175 @@
1
+ # Copyright (C) 2006-2007 Kurt Stephens <ruby-currency(at)umleta.com>
2
+ # See LICENSE.txt for details.
3
+
4
+
5
+ # Represents a currency.
6
+ #
7
+ # Currency objects are created on-demand by Currency::Currency::Factory.
8
+ #
9
+ # See Currency.get method.
10
+ #
11
+ class Currency::Currency
12
+ # Returns the ISO three-letter currency code as a symbol.
13
+ # e.g. :USD, :CAD, etc.
14
+ attr_reader :code
15
+
16
+ # The Currency's scale factor.
17
+ # e.g: the :USD scale factor is 100.
18
+ attr_reader :scale
19
+
20
+ # The Currency's scale factor.
21
+ # e.g: the :USD scale factor is 2, where 10 ^ 2 == 100.
22
+ attr_reader :scale_exp
23
+
24
+ # Used by Formatter.
25
+ attr_reader :format_right
26
+
27
+ # Used by Formatter.
28
+ attr_reader :format_left
29
+
30
+ # The Currency's symbol.
31
+ # e.g: USD symbol is '$'
32
+ attr_accessor :symbol
33
+
34
+ # The Currency's symbol as HTML.
35
+ # e.g: EUR symbol is '&#8364;' (:html &#8364; :) or '&euro;' (:html &euro; :)
36
+ attr_accessor :symbol_html
37
+
38
+ # The default Formatter.
39
+ attr_accessor :formatter
40
+
41
+ # The default parser.
42
+ attr_accessor :parser
43
+
44
+
45
+ # Create a new currency.
46
+ # This should only be called from Currency::Currency::Factory.
47
+ def initialize(code, symbol = nil, scale = 100)
48
+ self.code = code
49
+ self.symbol = symbol
50
+ self.scale = scale
51
+
52
+ @formatter =
53
+ @parser =
54
+ nil
55
+ end
56
+
57
+
58
+ # Returns the Currency object from the default Currency::Currency::Factory
59
+ # by its three-letter uppercase Symbol, such as :USD, or :CAD.
60
+ def self.get(code)
61
+ # $stderr.puts "#{self}.get(#{code.inspect})"
62
+ return nil unless code
63
+ return code if code.kind_of?(::Currency::Currency)
64
+ Factory.default.get_by_code(code)
65
+ end
66
+
67
+
68
+ # Internal method for converting currency codes to internal
69
+ # Symbol format.
70
+ def self.cast_code(x)
71
+ x = x.upcase.intern if x.kind_of?(String)
72
+ raise ::Currency::Exception::InvalidCurrencyCode, x unless x.kind_of?(Symbol)
73
+ raise ::Currency::Exception::InvalidCurrencyCode, x unless x.to_s.length == 3
74
+ x
75
+ end
76
+
77
+
78
+ # Returns the hash of the Currency's code.
79
+ def hash
80
+ @code.hash
81
+ end
82
+
83
+
84
+ # Returns true if the Currency's are equal.
85
+ def eql?(x)
86
+ self.class == x.class && @code == x.code
87
+ end
88
+
89
+
90
+ # Returns true if the Currency's are equal.
91
+ def ==(x)
92
+ self.class == x.class && @code == x.code
93
+ end
94
+
95
+
96
+ # Clients should never call this directly.
97
+ def code=(x)
98
+ x = self.class.cast_code(x) unless x.nil?
99
+ @code = x
100
+ #$stderr.puts "#{self}.code = #{@code}"; x
101
+ end
102
+
103
+
104
+ # Clients should never call this directly.
105
+ def scale=(x)
106
+ @scale = x
107
+ return x if x.nil?
108
+ @scale_exp = Integer(Math.log10(@scale));
109
+ @format_right = - @scale_exp
110
+ @format_left = @format_right - 1
111
+ x
112
+ end
113
+
114
+
115
+ # Parse a Money string in this Currency.
116
+ #
117
+ # See Currency::Parser#parse.
118
+ #
119
+ def parse(str, *opt)
120
+ parser_or_default.parse(str, *opt)
121
+ end
122
+
123
+
124
+ def parser_or_default
125
+ (@parser || ::Currency::Parser.default)
126
+ end
127
+
128
+
129
+ # Formats the Money value as a string using the current Formatter.
130
+ # See Currency::Formatter#format.
131
+ def format(m, *opt)
132
+ formatter_or_default.format(m, *opt)
133
+ end
134
+
135
+
136
+ def formatter_or_default
137
+ (@formatter || ::Currency::Formatter.default)
138
+ end
139
+
140
+
141
+ # Returns the Currency code as a String.
142
+ def to_s
143
+ @code.to_s
144
+ end
145
+
146
+
147
+ # Returns the default Factory's currency.
148
+ def self.default
149
+ Factory.default.currency
150
+ end
151
+
152
+
153
+ # Sets the default Factory's currency.
154
+ def self.default=(x)
155
+ x = self.get(x) unless x.kind_of?(self)
156
+ Factory.default.currency = x
157
+ end
158
+
159
+
160
+ # If selector is [A-Z][A-Z][A-Z], load the currency via Factory.default.
161
+ #
162
+ # Currency::Currency.USD
163
+ # => #<Currency::Currency:0xb7d0917c @formatter=nil, @scale_exp=2, @scale=100, @symbol="$", @format_left=-3, @code=:USD, @parser=nil, @format_right=-2>
164
+ #
165
+ def self.method_missing(sel, *args, &blk)
166
+ if args.size == 0 && (! block_given?) && /^[A-Z][A-Z][A-Z]$/.match(sel.to_s)
167
+ Factory.default.get_by_code(sel)
168
+ else
169
+ super
170
+ end
171
+ end
172
+
173
+ end # class
174
+
175
+
@@ -0,0 +1,6 @@
1
+ module Currency
2
+ CurrencyVersion = '0.5'
3
+ end
4
+ # DO NOT EDIT
5
+ # This file is auto-generated by build scripts.
6
+ # See: rake update_version
@@ -0,0 +1,119 @@
1
+ # Copyright (C) 2006-2007 Kurt Stephens <ruby-currency(at)umleta.com>
2
+ # See LICENSE.txt for details.
3
+
4
+ module Currency::Exception
5
+ # Base class for all Currency::Exception objects.
6
+ #
7
+ # raise Currency::Exception [ "msg", :opt1, 1, :opt2, 2 ]
8
+ #
9
+ class Base < ::Exception
10
+ EMPTY_HASH = { }.freeze
11
+
12
+ def initialize(arg1, *args)
13
+ case arg1
14
+ # [ description, ... ]
15
+ when Array
16
+ @opts = arg1
17
+ arg1 = arg1.shift
18
+ else
19
+ @opts = nil
20
+ end
21
+
22
+ case @opts
23
+ when Array
24
+ if @opts.size == 1 && @opts.first.kind_of?(Hash)
25
+ # [ description, { ... } ]
26
+ @opts = @opts.first
27
+ else
28
+ # [ description, :key, value, ... ]
29
+ @opts = Hash[*@opts]
30
+ end
31
+ end
32
+
33
+ case @opts
34
+ when Hash
35
+ @opts = @opts.dup.freeze
36
+ else
37
+ @opts = { :info => @opts }.freeze
38
+ end
39
+
40
+ @opts ||= EMPTY_HASH
41
+
42
+ super(arg1, *args)
43
+ end
44
+
45
+
46
+ def method_missing(sel, *args, &blk)
47
+ sel = sel.to_sym
48
+ if args.empty? && ! block_given? && @opts.key?(sel)
49
+ return @opts[sel]
50
+ end
51
+ super
52
+ end
53
+
54
+ def to_s
55
+ super + ": #{@opts.inspect}"
56
+ end
57
+
58
+ end
59
+
60
+ # Generic Error.
61
+ class Generic < Base
62
+ end
63
+
64
+ # Error during parsing of Money values from String.
65
+ class InvalidMoneyString < Base
66
+ end
67
+
68
+ # Error during coercion of external Money values.
69
+ class InvalidMoneyValue < Base
70
+ end
71
+
72
+ # Error in Currency code formeat.
73
+ class InvalidCurrencyCode < Base
74
+ end
75
+
76
+ # Error during conversion between currencies.
77
+ class IncompatibleCurrency < Base
78
+ end
79
+
80
+ # Error during locating currencies.
81
+ class MissingCurrency < Base
82
+ end
83
+
84
+ # Error if an Exchange is not defined.
85
+ class UndefinedExchange < Base
86
+ end
87
+
88
+ # Error if a Currency is unknown.
89
+ class UnknownCurrency < Base
90
+ end
91
+
92
+ # Error if an Exchange Rate Source cannot provide an Exchange::Rate.
93
+ class UnknownRate < Base
94
+ end
95
+
96
+ # Error if an Exchange Rate Source.
97
+ class RateSourceError < Base
98
+ end
99
+
100
+ # Error if an Exchange Rate Source cannot supply any rates.
101
+ class UnavailableRates < Base
102
+ end
103
+
104
+ # Error if an Exchange::Rate is not valid.
105
+ class InvalidRate < Base
106
+ end
107
+
108
+ # Error if a subclass is responsible for implementing a method.
109
+ class SubclassResponsibility < Base
110
+ end
111
+
112
+ # Error if some functionality is unimplemented
113
+ class Unimplemented < Base
114
+ end
115
+
116
+ # Error if reentrantancy is d
117
+ class InvalidReentrancy < Base
118
+ end
119
+ end # module
@@ -0,0 +1,157 @@
1
+ # Copyright (C) 2006-2007 Kurt Stephens <ruby-currency(at)umleta.com>
2
+ # See LICENSE.txt for details.
3
+
4
+ require 'currency/exchange/rate'
5
+ require 'currency/exchange/rate/source/base'
6
+
7
+ # The Currency::Exchange::Rate::Deriver class calculates derived rates
8
+ # from base Rates from a rate Source by pivoting against a pivot currency or by
9
+ # generating reciprocals.
10
+ #
11
+ class Currency::Exchange::Rate::Deriver < Currency::Exchange::Rate::Source::Base
12
+
13
+ # The source for base rates.
14
+ attr_accessor :source
15
+
16
+
17
+ def name
18
+ source.name
19
+ end
20
+
21
+
22
+ def initialize(opt = { })
23
+ @source = nil
24
+ @pivot_currency = nil
25
+ @derived_rates = { }
26
+ @all_rates = { }
27
+ super
28
+ end
29
+
30
+
31
+ def pivot_currency
32
+ @pivot_currency || @source.pivot_currency || :USD
33
+ end
34
+
35
+
36
+ # Return all currencies.
37
+ def currencies
38
+ @source.currencies
39
+ end
40
+
41
+
42
+ # Flush all cached Rates.
43
+ def clear_rates
44
+ @derived_rates.clear
45
+ @all_rates.clear
46
+ @source.clear_rates
47
+ super
48
+ end
49
+
50
+
51
+ # Returns all combinations of rates except identity rates.
52
+ def rates(time = nil)
53
+ time = time && normalize_time(time)
54
+ all_rates(time)
55
+ end
56
+
57
+
58
+ # Computes all rates.
59
+ # time is assumed to be normalized.
60
+ def all_rates(time = nil)
61
+ if x = @all_rates["#{time}"]
62
+ return x
63
+ end
64
+
65
+ x = @all_rates["#{time}"] = [ ]
66
+
67
+ currencies = self.currencies
68
+
69
+ currencies.each do | c1 |
70
+ currencies.each do | c2 |
71
+ next if c1 == c2
72
+ c1 = ::Currency::Currency.get(c1)
73
+ c2 = ::Currency::Currency.get(c2)
74
+ rate = rate(c1, c2, time)
75
+ x << rate
76
+ end
77
+ end
78
+
79
+ x
80
+ end
81
+
82
+
83
+ # Determines and creates the Rate between Currency c1 and c2.
84
+ #
85
+ # May attempt to use a pivot currency to bridge between
86
+ # rates.
87
+ #
88
+ def get_rate(c1, c2, time)
89
+ rate = get_rate_reciprocal(c1, c2, time)
90
+
91
+ # Attempt to use pivot_currency to bridge
92
+ # between Rates.
93
+ unless rate
94
+ pc = ::Currency::Currency.get(pivot_currency)
95
+
96
+ if pc &&
97
+ (rate_1 = get_rate_reciprocal(c1, pc, time)) &&
98
+ (rate_2 = get_rate_reciprocal(pc, c2, time))
99
+ c1_to_c2_rate = rate_1.rate * rate_2.rate
100
+ rate = new_rate(c1, c2,
101
+ c1_to_c2_rate,
102
+ rate_1.date || rate_2.date || time,
103
+ "pivot(#{pc.code},#{rate_1.derived || "#{rate_1.c1.code}#{rate_1.c2.code}"},#{rate_2.derived || "#{rate_2.c1}#{rate_2.c2}"})")
104
+ end
105
+ end
106
+
107
+ rate
108
+ end
109
+
110
+
111
+ # Get a matching base rate or its reciprocal.
112
+ def get_rate_reciprocal(c1, c2, time)
113
+ rate = get_rate_base_cached(c1, c2, time)
114
+ unless rate
115
+ if rate = get_rate_base_cached(c2, c1, time)
116
+ rate = (@rate["#{c1}:#{c2}:#{time}"] ||= rate.reciprocal)
117
+ end
118
+ end
119
+
120
+ rate
121
+ end
122
+
123
+
124
+ # Returns a cached base Rate.
125
+ #
126
+ def get_rate_base_cached(c1, c2, time)
127
+ rate = (@rate["#{c1}:#{c2}:#{time}"] ||= get_rate_base(c1, c2, time))
128
+ rate
129
+ end
130
+
131
+
132
+ # Returns a base Rate from the Source.
133
+ def get_rate_base(c1, c2, time)
134
+ if c1 == c2
135
+ # Identity rates are timeless.
136
+ new_rate(c1, c2, 1.0, nil, "identity")
137
+ else
138
+ source.rate(c1, c2, time)
139
+ end
140
+ end
141
+
142
+
143
+ def load_rates(time = nil)
144
+ all_rates(time)
145
+ end
146
+
147
+
148
+ # Returns true if the underlying rate provider is available.
149
+ def available?(time = nil)
150
+ source.available?(time)
151
+ end
152
+
153
+
154
+ end # class
155
+
156
+
157
+