mumboe-currency 0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/currency_historical_rate_load +105 -0
- data/examples/ex1.rb +13 -0
- data/examples/xe1.rb +20 -0
- data/lib/currency/active_record.rb +265 -0
- data/lib/currency/config.rb +91 -0
- data/lib/currency/core_extensions.rb +41 -0
- data/lib/currency/currency/factory.rb +228 -0
- data/lib/currency/currency.rb +175 -0
- data/lib/currency/currency_version.rb +6 -0
- data/lib/currency/exception.rb +119 -0
- data/lib/currency/exchange/rate/deriver.rb +157 -0
- data/lib/currency/exchange/rate/source/base.rb +166 -0
- data/lib/currency/exchange/rate/source/failover.rb +63 -0
- data/lib/currency/exchange/rate/source/federal_reserve.rb +160 -0
- data/lib/currency/exchange/rate/source/historical/rate.rb +184 -0
- data/lib/currency/exchange/rate/source/historical/rate_loader.rb +186 -0
- data/lib/currency/exchange/rate/source/historical/writer.rb +220 -0
- data/lib/currency/exchange/rate/source/historical.rb +79 -0
- data/lib/currency/exchange/rate/source/new_york_fed.rb +127 -0
- data/lib/currency/exchange/rate/source/provider.rb +120 -0
- data/lib/currency/exchange/rate/source/test.rb +50 -0
- data/lib/currency/exchange/rate/source/the_financials.rb +191 -0
- data/lib/currency/exchange/rate/source/timed_cache.rb +198 -0
- data/lib/currency/exchange/rate/source/xe.rb +165 -0
- data/lib/currency/exchange/rate/source.rb +89 -0
- data/lib/currency/exchange/rate.rb +214 -0
- data/lib/currency/exchange/time_quantitizer.rb +111 -0
- data/lib/currency/exchange.rb +50 -0
- data/lib/currency/formatter.rb +290 -0
- data/lib/currency/macro.rb +321 -0
- data/lib/currency/money.rb +295 -0
- data/lib/currency/money_helper.rb +13 -0
- data/lib/currency/parser.rb +151 -0
- data/lib/currency.rb +143 -0
- data/test/string_test.rb +54 -0
- data/test/test_base.rb +44 -0
- 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 => '؋'},
|
74
|
+
:ALL => {:symbol => 'Lek', :symbol_html => 'Lek'},
|
75
|
+
:ANG => {:symbol => 'ƒ', :symbol_html => 'ƒ'},
|
76
|
+
:ARS => {:symbol => '$', :symbol_html => '$'},
|
77
|
+
:AUD => {:symbol => '$', :symbol_html => '$'},
|
78
|
+
:AWG => {:symbol => 'ƒ', :symbol_html => 'ƒ'},
|
79
|
+
:AZN => {:symbol => 'ман', :symbol_html => 'ман'},
|
80
|
+
:BAM => {:symbol => 'KM', :symbol_html => 'KM'},
|
81
|
+
:BBD => {:symbol => '$', :symbol_html => '$'},
|
82
|
+
:BGN => {:symbol => 'лв', :symbol_html => 'лв'},
|
83
|
+
:BMD => {:symbol => '$', :symbol_html => '$'},
|
84
|
+
:BND => {:symbol => '$', :symbol_html => '$'},
|
85
|
+
:BOB => {:symbol => '$b', :symbol_html => '$b'},
|
86
|
+
:BRL => {:symbol => 'R$', :symbol_html => 'R$'},
|
87
|
+
:BSD => {:symbol => '$', :symbol_html => '$'},
|
88
|
+
:BWP => {:symbol => 'P', :symbol_html => 'P'},
|
89
|
+
:BYR => {:symbol => 'p.', :symbol_html => 'p.'},
|
90
|
+
:BZD => {:symbol => 'BZ$', :symbol_html => 'BZ$'},
|
91
|
+
:CAD => {:symbol => '$', :symbol_html => '$'},
|
92
|
+
:CHF => {:symbol => 'CHF', :symbol_html => 'CHF'},
|
93
|
+
:CLP => {:symbol => '$', :symbol_html => '$'},
|
94
|
+
:CNY => {:symbol => '¥', :symbol_html => '¥'},
|
95
|
+
:COP => {:symbol => '$', :symbol_html => '$'},
|
96
|
+
:CRC => {:symbol => '₡', :symbol_html => '₡'},
|
97
|
+
:CUP => {:symbol => '₱', :symbol_html => '₱'},
|
98
|
+
:CZK => {:symbol => 'Kč', :symbol_html => 'Kč'},
|
99
|
+
:DKK => {:symbol => 'kr', :symbol_html => 'kr'},
|
100
|
+
:DOP => {:symbol => 'RD$', :symbol_html => 'RD$'},
|
101
|
+
:EEK => {:symbol => 'kr', :symbol_html => 'kr'},
|
102
|
+
:EGP => {:symbol => '£', :symbol_html => '£'},
|
103
|
+
:EUR => {:symbol => '€', :symbol_html => '€'},
|
104
|
+
:FJD => {:symbol => '$', :symbol_html => '$'},
|
105
|
+
:FKP => {:symbol => '£', :symbol_html => '£'},
|
106
|
+
:GBP => {:symbol => '£', :symbol_html => '£'},
|
107
|
+
:GGP => {:symbol => '£', :symbol_html => '£'},
|
108
|
+
:GHC => {:symbol => '¢', :symbol_html => '¢'},
|
109
|
+
:GIP => {:symbol => '£', :symbol_html => '£'},
|
110
|
+
:GTQ => {:symbol => 'Q', :symbol_html => 'Q'},
|
111
|
+
:GYD => {:symbol => '$', :symbol_html => '$'},
|
112
|
+
:HKD => {:symbol => '$', :symbol_html => '$'},
|
113
|
+
:HNL => {:symbol => 'L', :symbol_html => 'L'},
|
114
|
+
:HRK => {:symbol => 'kn', :symbol_html => 'kn'},
|
115
|
+
:HUF => {:symbol => 'Ft', :symbol_html => 'Ft'},
|
116
|
+
:IDR => {:symbol => 'Rp', :symbol_html => 'Rp'},
|
117
|
+
:ILS => {:symbol => '₪', :symbol_html => '₪'},
|
118
|
+
:IMP => {:symbol => '£', :symbol_html => '£'},
|
119
|
+
:INR => {:symbol => '₨', :symbol_html => '₨'},
|
120
|
+
:IRR => {:symbol => '﷼', :symbol_html => '﷼'},
|
121
|
+
:ISK => {:symbol => 'kr', :symbol_html => 'kr'},
|
122
|
+
:JEP => {:symbol => '£', :symbol_html => '£'},
|
123
|
+
:JMD => {:symbol => 'J$', :symbol_html => 'J$'},
|
124
|
+
:JPY => {:symbol => '¥', :symbol_html => '¥'},
|
125
|
+
:KGS => {:symbol => 'лв', :symbol_html => 'лв'},
|
126
|
+
:KHR => {:symbol => '៛', :symbol_html => '៛'},
|
127
|
+
:KPW => {:symbol => '₩', :symbol_html => '₩'},
|
128
|
+
:KRW => {:symbol => '₩', :symbol_html => '₩'},
|
129
|
+
:KYD => {:symbol => '$', :symbol_html => '$'},
|
130
|
+
:KZT => {:symbol => 'лв', :symbol_html => 'лв'},
|
131
|
+
:LAK => {:symbol => '₭', :symbol_html => '₭'},
|
132
|
+
:LBP => {:symbol => '£', :symbol_html => '£'},
|
133
|
+
:LKR => {:symbol => '₨', :symbol_html => '₨'},
|
134
|
+
:LRD => {:symbol => '$', :symbol_html => '$'},
|
135
|
+
:LTL => {:symbol => 'Lt', :symbol_html => 'Lt'},
|
136
|
+
:LVL => {:symbol => 'Ls', :symbol_html => 'Ls'},
|
137
|
+
:MKD => {:symbol => 'ден', :symbol_html => 'ден'},
|
138
|
+
:MNT => {:symbol => '₮', :symbol_html => '₮'},
|
139
|
+
:MUR => {:symbol => '₨', :symbol_html => '₨'},
|
140
|
+
:MXN => {:symbol => '$', :symbol_html => '$'},
|
141
|
+
:MYR => {:symbol => 'RM', :symbol_html => 'RM'},
|
142
|
+
:MZN => {:symbol => 'MT', :symbol_html => 'MT'},
|
143
|
+
:NAD => {:symbol => '$', :symbol_html => '$'},
|
144
|
+
:NGN => {:symbol => '₦', :symbol_html => '₦'},
|
145
|
+
:NIO => {:symbol => 'C$', :symbol_html => 'C$'},
|
146
|
+
:NOK => {:symbol => 'kr', :symbol_html => 'kr'},
|
147
|
+
:NPR => {:symbol => '₨', :symbol_html => '₨'},
|
148
|
+
:NZD => {:symbol => '$', :symbol_html => '$'},
|
149
|
+
:OMR => {:symbol => '﷼', :symbol_html => '﷼'},
|
150
|
+
:PAB => {:symbol => 'B/.', :symbol_html => 'B/.'},
|
151
|
+
:PEN => {:symbol => 'S/.', :symbol_html => 'S/.'},
|
152
|
+
:PHP => {:symbol => 'Php', :symbol_html => 'Php'},
|
153
|
+
:PKR => {:symbol => '₨', :symbol_html => '₨'},
|
154
|
+
:PLN => {:symbol => 'zł', :symbol_html => 'zł'},
|
155
|
+
:PYG => {:symbol => 'Gs', :symbol_html => 'Gs'},
|
156
|
+
:QAR => {:symbol => '﷼', :symbol_html => '﷼'},
|
157
|
+
:RON => {:symbol => 'lei', :symbol_html => 'lei'},
|
158
|
+
:RSD => {:symbol => 'Дин.', :symbol_html => 'Дин.'},
|
159
|
+
:RUB => {:symbol => 'руб', :symbol_html => 'руб'},
|
160
|
+
:SAR => {:symbol => '﷼', :symbol_html => '﷼'},
|
161
|
+
:SBD => {:symbol => '$', :symbol_html => '$'},
|
162
|
+
:SCR => {:symbol => '₨', :symbol_html => '₨'},
|
163
|
+
:SEK => {:symbol => 'kr', :symbol_html => 'kr'},
|
164
|
+
:SGD => {:symbol => '$', :symbol_html => '$'},
|
165
|
+
:SHP => {:symbol => '£', :symbol_html => '£'},
|
166
|
+
:SOS => {:symbol => 'S', :symbol_html => 'S'},
|
167
|
+
:SRD => {:symbol => '$', :symbol_html => '$'},
|
168
|
+
:SVC => {:symbol => '$', :symbol_html => '$'},
|
169
|
+
:SYP => {:symbol => '£', :symbol_html => '£'},
|
170
|
+
:THB => {:symbol => '฿', :symbol_html => '฿'},
|
171
|
+
:TRL => {:symbol => '₤', :symbol_html => '₤'},
|
172
|
+
:TRY => {:symbol => 'TL', :symbol_html => 'TL'},
|
173
|
+
:TTD => {:symbol => 'TT$', :symbol_html => 'TT$'},
|
174
|
+
:TVD => {:symbol => '$', :symbol_html => '$'},
|
175
|
+
:TWD => {:symbol => 'NT$', :symbol_html => 'NT$'},
|
176
|
+
:UAH => {:symbol => '₴', :symbol_html => '₴'},
|
177
|
+
:USD => {:symbol => '$', :symbol_html => '$'},
|
178
|
+
:UYU => {:symbol => '$U', :symbol_html => '$U'},
|
179
|
+
:UZS => {:symbol => 'лв', :symbol_html => 'лв'},
|
180
|
+
:VEF => {:symbol => 'Bs', :symbol_html => 'Bs'},
|
181
|
+
:VND => {:symbol => '₫', :symbol_html => '₫'},
|
182
|
+
:XCD => {:symbol => '$', :symbol_html => '$'},
|
183
|
+
:YER => {:symbol => '﷼', :symbol_html => '﷼'},
|
184
|
+
:ZAR => {:symbol => 'R', :symbol_html => 'R'},
|
185
|
+
:ZWD => {:symbol => 'Z$', :symbol_html => 'Z$'},
|
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 '€' (:html € :) or '€' (:html € :)
|
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,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
|
+
|