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