mumboe-currency 0.5

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 (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
+