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,295 @@
|
|
1
|
+
# Copyright (C) 2006-2007 Kurt Stephens <ruby-currency(at)umleta.com>
|
2
|
+
# See LICENSE.txt for details.
|
3
|
+
|
4
|
+
#
|
5
|
+
# Represents an amount of money in a particular currency.
|
6
|
+
#
|
7
|
+
# A Money object stores its value using a scaled Integer representation
|
8
|
+
# and a Currency object.
|
9
|
+
#
|
10
|
+
# A Money object also has a time, which is used in conversions
|
11
|
+
# against historical exchange rates.
|
12
|
+
#
|
13
|
+
class Currency::Money
|
14
|
+
include Comparable
|
15
|
+
|
16
|
+
@@default_time = nil
|
17
|
+
def self.default_time
|
18
|
+
@@default_time
|
19
|
+
end
|
20
|
+
def self.default_time=(x)
|
21
|
+
@@default_time = x
|
22
|
+
end
|
23
|
+
|
24
|
+
@@empty_hash = { }
|
25
|
+
@@empty_hash.freeze
|
26
|
+
|
27
|
+
#
|
28
|
+
# DO NOT CALL THIS DIRECTLY:
|
29
|
+
#
|
30
|
+
# See Currency.Money() function.
|
31
|
+
#
|
32
|
+
# Construct a Money value object
|
33
|
+
# from a pre-scaled external representation:
|
34
|
+
# where x is a Float, Integer, String, etc.
|
35
|
+
#
|
36
|
+
# If a currency is not specified, Currency.default is used.
|
37
|
+
#
|
38
|
+
# x.Money_rep(currency)
|
39
|
+
#
|
40
|
+
# is invoked to coerce x into a Money representation value.
|
41
|
+
#
|
42
|
+
# For example:
|
43
|
+
#
|
44
|
+
# 123.Money_rep(:USD) => 12300
|
45
|
+
#
|
46
|
+
# Because the USD Currency object has a #scale of 100
|
47
|
+
#
|
48
|
+
# See #Money_rep(currency) mixin.
|
49
|
+
#
|
50
|
+
def initialize(x, currency = nil, time = nil)
|
51
|
+
opts ||= @@empty_hash
|
52
|
+
|
53
|
+
# Set ivars.
|
54
|
+
currency = ::Currency::Currency.get(currency)
|
55
|
+
@currency = currency
|
56
|
+
@time = time || ::Currency::Money.default_time
|
57
|
+
@time = ::Currency::Money.now if @time == :now
|
58
|
+
if x.kind_of?(String)
|
59
|
+
if currency
|
60
|
+
m = currency.parser_or_default.parse(x, :currency => currency)
|
61
|
+
else
|
62
|
+
m = ::Currency::Parser.default.parse(x)
|
63
|
+
end
|
64
|
+
@currency = m.currency unless @currency
|
65
|
+
@time = m.time if m.time
|
66
|
+
@rep = m.rep
|
67
|
+
else
|
68
|
+
@currency = ::Currency::Currency.default unless @currency
|
69
|
+
@rep = x.Money_rep(@currency)
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
# Returns a Time.new
|
75
|
+
# Can be modifed for special purposes.
|
76
|
+
def self.now
|
77
|
+
Time.new
|
78
|
+
end
|
79
|
+
|
80
|
+
# Compatibility with Money package.
|
81
|
+
def self.us_dollar(x)
|
82
|
+
self.new(x, :USD)
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
# Compatibility with Money package.
|
87
|
+
def cents
|
88
|
+
@rep
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
# Construct from post-scaled internal representation.
|
93
|
+
def self.new_rep(r, currency = nil, time = nil)
|
94
|
+
x = self.new(0, currency, time)
|
95
|
+
x.set_rep(r)
|
96
|
+
x
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
# Construct from post-scaled internal representation.
|
101
|
+
# using the same currency.
|
102
|
+
#
|
103
|
+
# x = Currency.Money("1.98", :USD)
|
104
|
+
# x.new_rep(123) => USD $1.23
|
105
|
+
#
|
106
|
+
# time defaults to self.time.
|
107
|
+
def new_rep(r, time = nil)
|
108
|
+
time ||= @time
|
109
|
+
x = self.class.new(0, @currency, time)
|
110
|
+
x.set_rep(r)
|
111
|
+
x
|
112
|
+
end
|
113
|
+
|
114
|
+
# Do not call this method directly.
|
115
|
+
# CLIENTS SHOULD NEVER CALL set_rep DIRECTLY.
|
116
|
+
# You have been warned in ALL CAPS.
|
117
|
+
def set_rep(r) # :nodoc:
|
118
|
+
r = r.to_i unless r.kind_of?(Integer)
|
119
|
+
@rep = r
|
120
|
+
end
|
121
|
+
|
122
|
+
# Do not call this method directly.
|
123
|
+
# CLIENTS SHOULD NEVER CALL set_time DIRECTLY.
|
124
|
+
# You have been warned in ALL CAPS.
|
125
|
+
def set_time(time) # :nodoc:
|
126
|
+
@time = time
|
127
|
+
end
|
128
|
+
|
129
|
+
# Returns the Money representation (usually an Integer).
|
130
|
+
def rep
|
131
|
+
@rep
|
132
|
+
end
|
133
|
+
|
134
|
+
# Get the Money's Currency.
|
135
|
+
def currency
|
136
|
+
@currency
|
137
|
+
end
|
138
|
+
|
139
|
+
# Get the Money's time.
|
140
|
+
def time
|
141
|
+
@time
|
142
|
+
end
|
143
|
+
|
144
|
+
# Convert Money to another Currency.
|
145
|
+
# currency can be a Symbol or a Currency object.
|
146
|
+
# If currency is nil, the Currency.default is used.
|
147
|
+
def convert(currency, time = nil)
|
148
|
+
currency = ::Currency::Currency.default if currency.nil?
|
149
|
+
currency = ::Currency::Currency.get(currency) unless currency.kind_of?(Currency)
|
150
|
+
if @currency == currency
|
151
|
+
self
|
152
|
+
else
|
153
|
+
time = self.time if time == :money
|
154
|
+
::Currency::Exchange::Rate::Source.current.convert(self, currency, time)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
|
159
|
+
# Hash for hash table: both value and currency.
|
160
|
+
# See #eql? below.
|
161
|
+
def hash
|
162
|
+
@rep.hash ^ @currency.hash
|
163
|
+
end
|
164
|
+
|
165
|
+
|
166
|
+
# True if money values have the same value and currency.
|
167
|
+
def eql?(x)
|
168
|
+
self.class == x.class &&
|
169
|
+
@rep == x.rep &&
|
170
|
+
@currency == x.currency
|
171
|
+
end
|
172
|
+
|
173
|
+
# True if money values have the same value and currency.
|
174
|
+
def ==(x)
|
175
|
+
self.class == x.class &&
|
176
|
+
@rep == x.rep &&
|
177
|
+
@currency == x.currency
|
178
|
+
end
|
179
|
+
|
180
|
+
# Compares Money values.
|
181
|
+
# Will convert x to self.currency before comparision.
|
182
|
+
def <=>(x)
|
183
|
+
if @currency == x.currency
|
184
|
+
@rep <=> x.rep
|
185
|
+
else
|
186
|
+
@rep <=> convert(@currency, @time).rep
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
|
191
|
+
# - Money => Money
|
192
|
+
#
|
193
|
+
# Negates a Money value.
|
194
|
+
def -@
|
195
|
+
new_rep(- @rep)
|
196
|
+
end
|
197
|
+
|
198
|
+
# Money + (Money | Number) => Money
|
199
|
+
#
|
200
|
+
# Right side may be coerced to left side's Currency.
|
201
|
+
def +(x)
|
202
|
+
new_rep(@rep + x.Money_rep(@currency))
|
203
|
+
end
|
204
|
+
|
205
|
+
# Money - (Money | Number) => Money
|
206
|
+
#
|
207
|
+
# Right side may be coerced to left side's Currency.
|
208
|
+
def -(x)
|
209
|
+
new_rep(@rep - x.Money_rep(@currency))
|
210
|
+
end
|
211
|
+
|
212
|
+
# Money * Number => Money
|
213
|
+
#
|
214
|
+
# Right side must be Number.
|
215
|
+
def *(x)
|
216
|
+
new_rep(@rep * x)
|
217
|
+
end
|
218
|
+
|
219
|
+
# Money / Money => Float (ratio)
|
220
|
+
# Money / Number => Money
|
221
|
+
#
|
222
|
+
# Right side must be Money or Number.
|
223
|
+
# Right side Integers are not coerced to Float before
|
224
|
+
# division.
|
225
|
+
def /(x)
|
226
|
+
if x.kind_of?(self.class)
|
227
|
+
(@rep.to_f) / (x.Money_rep(@currency).to_f)
|
228
|
+
else
|
229
|
+
new_rep(@rep / x)
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
# Formats the Money value as a String using the Currency's Formatter.
|
234
|
+
def format(*opt)
|
235
|
+
@currency.format(self, *opt)
|
236
|
+
end
|
237
|
+
|
238
|
+
# Formats the Money value as a String.
|
239
|
+
def to_s(*opt)
|
240
|
+
@currency.format(self, *opt)
|
241
|
+
end
|
242
|
+
|
243
|
+
# Coerces the Money's value to a Float.
|
244
|
+
# May cause loss of precision.
|
245
|
+
def to_f
|
246
|
+
Float(@rep) / @currency.scale
|
247
|
+
end
|
248
|
+
|
249
|
+
# Coerces the Money's value to an Integer.
|
250
|
+
# May cause loss of precision.
|
251
|
+
def to_i
|
252
|
+
@rep / @currency.scale
|
253
|
+
end
|
254
|
+
|
255
|
+
# True if the Money's value is zero.
|
256
|
+
def zero?
|
257
|
+
@rep == 0
|
258
|
+
end
|
259
|
+
|
260
|
+
# True if the Money's value is greater than zero.
|
261
|
+
def positive?
|
262
|
+
@rep > 0
|
263
|
+
end
|
264
|
+
|
265
|
+
# True if the Money's value is less than zero.
|
266
|
+
def negative?
|
267
|
+
@rep < 0
|
268
|
+
end
|
269
|
+
|
270
|
+
# Returns the Money's value representation in another currency.
|
271
|
+
def Money_rep(currency, time = nil)
|
272
|
+
# Attempt conversion?
|
273
|
+
if @currency != currency || (time && @time != time)
|
274
|
+
self.convert(currency, time).rep
|
275
|
+
# raise ::Currency::Exception::Generic, "Incompatible Currency: #{@currency} != #{currency}"
|
276
|
+
else
|
277
|
+
@rep
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
# Basic inspection, with symbol, currency code and time.
|
282
|
+
# The standard #inspect method is available as #inspect_deep.
|
283
|
+
def inspect(*opts)
|
284
|
+
self.format(:symbol => true, :code => true, :time => true)
|
285
|
+
end
|
286
|
+
|
287
|
+
# How to alias a method defined in an object superclass in a different class:
|
288
|
+
define_method(:inspect_deep, Object.instance_method(:inspect))
|
289
|
+
# How call a method defined in a superclass from a method with a different name:
|
290
|
+
# def inspect_deep(*opts)
|
291
|
+
# self.class.superclass.instance_method(:inspect).bind(self).call
|
292
|
+
# end
|
293
|
+
|
294
|
+
end # class
|
295
|
+
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# Copyright (C) 2006-2007 Kurt Stephens <ruby-currency(at)umleta.com>
|
2
|
+
# See LICENSE.txt for details.
|
3
|
+
|
4
|
+
module ActionView::Helpers::MoneyHelper
|
5
|
+
# Creates a suitable HTML element for a Money value field.
|
6
|
+
def money_field(object, method, options = {})
|
7
|
+
InstanceTag.new(object, method, self).to_input_field_tag("text", options)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
ActionView::Base.load_helper(File.dirname(__FILE__))
|
13
|
+
|
@@ -0,0 +1,151 @@
|
|
1
|
+
# Copyright (C) 2006-2007 Kurt Stephens <ruby-currency(at)umleta.com>
|
2
|
+
# See LICENSE.txt for details.
|
3
|
+
|
4
|
+
|
5
|
+
require 'rss/rss' # Time#xmlschema
|
6
|
+
|
7
|
+
|
8
|
+
# This class parses a Money value from a String.
|
9
|
+
# Each Currency has a default Parser.
|
10
|
+
class Currency::Parser
|
11
|
+
|
12
|
+
SYMBOLS_FOR_PATTERN = Currency::Currency::Factory::UNIQUE_SYMBOLS.collect {|symbol| symbol.split("").collect {|c| c =~ /[a-z]/i ? c : '\\'+c }.join }.join("|")
|
13
|
+
VALID_MONEY_PATTERN = /^(([a-zA-z][a-zA-z][a-zA-z])|(#{SYMBOLS_FOR_PATTERN}))?\s*([\-\+\d,\.]+)\s*(([a-zA-z][a-zA-z][a-zA-z])|(#{SYMBOLS_FOR_PATTERN}))?(\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d(\.\d+)?Z)?$/
|
14
|
+
|
15
|
+
# The default Currency to use if no Currency is specified.
|
16
|
+
attr_accessor :currency
|
17
|
+
|
18
|
+
# If true and a parsed string contains a ISO currency code
|
19
|
+
# that is not the same as currency,
|
20
|
+
# #parse() will raise IncompatibleCurrency.
|
21
|
+
# Defaults to false.
|
22
|
+
attr_accessor :enforce_currency
|
23
|
+
|
24
|
+
# The default Time to use if no Time is specified in the string.
|
25
|
+
# If :now, time is set to Time.new.
|
26
|
+
attr_accessor :time
|
27
|
+
|
28
|
+
@@default = nil
|
29
|
+
# Get the default Formatter.
|
30
|
+
def self.default
|
31
|
+
@@default ||=
|
32
|
+
self.new
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
# Set the default Formatter.
|
37
|
+
def self.default=(x)
|
38
|
+
@@default = x
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
def initialize(opt = { })
|
43
|
+
@time =
|
44
|
+
@enforce_currency =
|
45
|
+
@currency =
|
46
|
+
nil
|
47
|
+
opt.each_pair{ | k, v | self.send("#{k}=", v) }
|
48
|
+
end
|
49
|
+
|
50
|
+
def _parse(str) # :nodoc:
|
51
|
+
x = str
|
52
|
+
md = nil
|
53
|
+
time = nil
|
54
|
+
|
55
|
+
convert_currency = nil
|
56
|
+
md = VALID_MONEY_PATTERN.match(x)
|
57
|
+
if md && !@currency
|
58
|
+
symbol = md[3] || md[7]
|
59
|
+
code = md[2] || md[6]
|
60
|
+
curr = Currency::Currency.get(code ? code.upcase : nil) || (symbol ? Currency::Currency::Factory.get_currency_from_symbol(symbol) : Currency::Currency.default)
|
61
|
+
x = md[4]
|
62
|
+
time = Time.xmlschema(md[8]) if md[8]
|
63
|
+
time ||= @time
|
64
|
+
time = Time.new if time == :now
|
65
|
+
currency = curr
|
66
|
+
else
|
67
|
+
currency = @currency || ::Currency::Currency.default
|
68
|
+
currency = ::Currency::Currency.get(currency)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Remove placeholders and symbol.
|
72
|
+
x.gsub!(/[, ]/, '')
|
73
|
+
symbol = currency.symbol if symbol.nil?
|
74
|
+
x.gsub!(symbol, '') if symbol
|
75
|
+
|
76
|
+
# Match: whole Currency value.
|
77
|
+
if md = /^([-+]?\d+)\.?$/.match(x)
|
78
|
+
x = ::Currency::Money.new_rep(md[1].to_i * currency.scale, currency, @time)
|
79
|
+
|
80
|
+
# Match: fractional Currency value.
|
81
|
+
elsif md = /^([-+]?)(\d*)\.(\d+)$/.match(x)
|
82
|
+
sign = md[1]
|
83
|
+
whole = md[2]
|
84
|
+
part = md[3]
|
85
|
+
|
86
|
+
if part.length != currency.scale
|
87
|
+
currency.scale = 10**part.length if part.length > 1
|
88
|
+
|
89
|
+
# Pad decimal places with additional '0'
|
90
|
+
while part.length < currency.scale_exp
|
91
|
+
part << '0'
|
92
|
+
end
|
93
|
+
|
94
|
+
# Truncate to Currency's decimal size.
|
95
|
+
part = part[0 ... currency.scale_exp]
|
96
|
+
end
|
97
|
+
|
98
|
+
# Put the string back together:
|
99
|
+
whole = sign + whole + part
|
100
|
+
|
101
|
+
x = whole.to_i
|
102
|
+
x = ::Currency::Money.new_rep(x, currency, time)
|
103
|
+
else
|
104
|
+
raise ::Currency::Exception::InvalidMoneyString,
|
105
|
+
[
|
106
|
+
"#{str.inspect} #{currency} #{x.inspect}",
|
107
|
+
:string, str,
|
108
|
+
:currency, currency,
|
109
|
+
:state, x,
|
110
|
+
]
|
111
|
+
end
|
112
|
+
|
113
|
+
# Do conversion.
|
114
|
+
if convert_currency
|
115
|
+
x = x.convert(convert_currency)
|
116
|
+
end
|
117
|
+
|
118
|
+
x
|
119
|
+
end
|
120
|
+
|
121
|
+
@@empty_hash = { }
|
122
|
+
@@empty_hash.freeze
|
123
|
+
|
124
|
+
# Parse a Money string in this Currency.
|
125
|
+
#
|
126
|
+
# "123.45".money # Using default Currency.
|
127
|
+
# => USD $123.45
|
128
|
+
#
|
129
|
+
# "$123.45 USD".money # Explicit Currency.
|
130
|
+
# => USD $123.45
|
131
|
+
#
|
132
|
+
# "CAD 123.45".money
|
133
|
+
# => CAD $123.45
|
134
|
+
#
|
135
|
+
# "123.45 CAD".money(:USD) # Incompatible explicit Currency.
|
136
|
+
# !!! "123.45 CAD" USD (Currency::Exception::IncompatibleCurrency)
|
137
|
+
#
|
138
|
+
def parse(str, opt = @@empty_hash)
|
139
|
+
prs = self
|
140
|
+
|
141
|
+
unless opt.empty?
|
142
|
+
prs = prs.clone
|
143
|
+
opt.each_pair{ | k, v | prs.send("#{k}=", v) }
|
144
|
+
end
|
145
|
+
|
146
|
+
prs._parse(str)
|
147
|
+
end
|
148
|
+
|
149
|
+
end # class
|
150
|
+
|
151
|
+
|
data/lib/currency.rb
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
# Copyright (C) 2006-2007 Kurt Stephens <ruby-currency(at)umleta.com>
|
2
|
+
#
|
3
|
+
# See LICENSE.txt for details.
|
4
|
+
#
|
5
|
+
# = Currency
|
6
|
+
#
|
7
|
+
# The Currency package provides an object-oriented model of:
|
8
|
+
#
|
9
|
+
# * currencies
|
10
|
+
# * exchanges
|
11
|
+
# * exchange rates
|
12
|
+
# * exchange rate sources
|
13
|
+
# * monetary values
|
14
|
+
#
|
15
|
+
# The core classes are:
|
16
|
+
#
|
17
|
+
# * Currency::Money - uses a scaled integer representation of a monetary value and performs accurate conversions to and from string values.
|
18
|
+
# * Currency::Currency - provides an object-oriented representation of a currency.
|
19
|
+
# * Currency::Exchange::Base - the base class for a currency exchange rate provider.
|
20
|
+
# * Currency::Exchange::Rate::Source::Base - the base class for an on-demand currency exchange rate data provider.
|
21
|
+
# * Currency::Exchange::Rate::Source::Provider - the base class for a bulk exchange rate data provider.
|
22
|
+
# * Currency::Exchange::Rate - represents a exchange rate between two currencies.
|
23
|
+
#
|
24
|
+
#
|
25
|
+
# The example below uses Currency::Exchange::Xe to automatically get
|
26
|
+
# exchange rates from http://xe.com/ :
|
27
|
+
#
|
28
|
+
# require 'currency'
|
29
|
+
# require 'currency/exchange/rate/deriver'
|
30
|
+
# require 'currency/exchange/rate/source/xe'
|
31
|
+
# require 'currency/exchange/rate/source/timed_cache'
|
32
|
+
#
|
33
|
+
# # Rate source initialization
|
34
|
+
# provider = Currency::Exchange::Rate::Source::Xe.new
|
35
|
+
# deriver = Currency::Exchange::Rate::Deriver.new(:source => provider)
|
36
|
+
# cache = Currency::Exchange::Rate::Source::TimedCache.new(:source => deriver)
|
37
|
+
# Currency::Exchange::Rate::Source.default = cache
|
38
|
+
#
|
39
|
+
# usd = Currency::Money('6.78', :USD)
|
40
|
+
# puts "usd = #{usd.format}"
|
41
|
+
# cad = usd.convert(:CAD)
|
42
|
+
# puts "cad = #{cad.format}"
|
43
|
+
#
|
44
|
+
# == ActiveRecord Suppport
|
45
|
+
#
|
46
|
+
# This package also contains ActiveRecord support for money values:
|
47
|
+
#
|
48
|
+
# require 'currency'
|
49
|
+
# require 'currency/active_record'
|
50
|
+
#
|
51
|
+
# class Entry < ActiveRecord::Base
|
52
|
+
# money :amount
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# In the example above, the entries.amount database column is an INTEGER that represents US cents.
|
56
|
+
# The currency code of the money value can be stored in an additional database column or a default currency can be used.
|
57
|
+
#
|
58
|
+
# == Recent Enhancements
|
59
|
+
#
|
60
|
+
# === Storage and retrival of historical exchange rates
|
61
|
+
#
|
62
|
+
# * See Currency::Exchange::Rate::Source::Historical
|
63
|
+
# * See Currency::Exchange::Rate::Source::Historical::Writer
|
64
|
+
#
|
65
|
+
# === Automatic derivation of rates from base rates.
|
66
|
+
#
|
67
|
+
# * See Currency::Exchange::Rate::Deriver
|
68
|
+
#
|
69
|
+
# === Rate caching
|
70
|
+
#
|
71
|
+
# * See Currency::Exchange::Rate::Source::TimedCache
|
72
|
+
#
|
73
|
+
# === Rate Providers
|
74
|
+
#
|
75
|
+
# * See Currency::Exchange::Rate::Source::Xe
|
76
|
+
# * See Currency::Exchange::Rate::Source::NewYorkFed
|
77
|
+
# * See Currency::Exchange::Rate::Source::TheFinancials
|
78
|
+
#
|
79
|
+
# === Customizable formatting and parsing
|
80
|
+
#
|
81
|
+
# * See Currency::Formatter
|
82
|
+
# * See Currency::Parser
|
83
|
+
#
|
84
|
+
# == Future Enhancements
|
85
|
+
#
|
86
|
+
# * Support for inflationary rates within a currency, e.g. $10 USD in the year 1955 converted to 2006 USD.
|
87
|
+
#
|
88
|
+
# == SVN Repo
|
89
|
+
#
|
90
|
+
# svn checkout svn://rubyforge.org/var/svn/currency/currency/trunk
|
91
|
+
#
|
92
|
+
# == Examples
|
93
|
+
#
|
94
|
+
# See the examples/ and test/ directorys
|
95
|
+
#
|
96
|
+
# == Author
|
97
|
+
#
|
98
|
+
# Kurt Stephens http://kurtstephens.com
|
99
|
+
#
|
100
|
+
# == Support
|
101
|
+
#
|
102
|
+
# http://rubyforge.org/forum/forum.php?forum_id=7643
|
103
|
+
#
|
104
|
+
# == Copyright
|
105
|
+
#
|
106
|
+
# Copyright (C) 2006-2007 Kurt Stephens <ruby-currency(at)umleta.com>
|
107
|
+
#
|
108
|
+
# See LICENSE.txt for details.
|
109
|
+
#
|
110
|
+
module Currency
|
111
|
+
# Use this function instead of Money#new:
|
112
|
+
#
|
113
|
+
# Currency::Money("12.34", :CAD)
|
114
|
+
#
|
115
|
+
# Do not do this:
|
116
|
+
#
|
117
|
+
# Currency::Money.new("12.34", :CAD)
|
118
|
+
#
|
119
|
+
# See Money#new.
|
120
|
+
def self.Money(*opts)
|
121
|
+
Money.new(*opts)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
$:.unshift(File.expand_path(File.dirname(__FILE__))) unless $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
126
|
+
|
127
|
+
require 'currency/currency_version'
|
128
|
+
require 'currency/config'
|
129
|
+
require 'currency/exception'
|
130
|
+
require 'currency/money'
|
131
|
+
require 'currency/currency'
|
132
|
+
require 'currency/currency/factory'
|
133
|
+
require 'currency/money'
|
134
|
+
require 'currency/formatter'
|
135
|
+
require 'currency/parser'
|
136
|
+
require 'currency/exchange'
|
137
|
+
require 'currency/exchange/rate'
|
138
|
+
require 'currency/exchange/rate/deriver'
|
139
|
+
require 'currency/exchange/rate/source'
|
140
|
+
require 'currency/exchange/rate/source/test'
|
141
|
+
require 'currency/exchange/time_quantitizer'
|
142
|
+
require 'currency/core_extensions'
|
143
|
+
|
data/test/string_test.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
# various tests to verify latest revisions
|
2
|
+
require "test/test_base"
|
3
|
+
require 'currency'
|
4
|
+
|
5
|
+
module Currency
|
6
|
+
|
7
|
+
class StringTest < TestBase
|
8
|
+
def setup
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_beginning_with_code
|
13
|
+
assert_equal "¥424,535,953.00", "CNY424,535,953".money.to_s
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_beginning_with_symbol
|
17
|
+
assert_equal "₡42,525,924.87", "₡ 42525924.87".money.to_s
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_ending_with_code
|
21
|
+
assert_equal "₱324,402,482.00", "324402482 CUP".money.to_s
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_ending_with_symbol
|
25
|
+
assert_equal "IRR 2,445.00", "2445﷼".money.to_s(:symbol => false, :code => true)
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_unknown_code
|
29
|
+
assert_equal "CFA 15,000.00", "15000CFA".money.to_s(:symbol => true, :code => true)
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_invalid_symbol
|
33
|
+
assert_raise ::Currency::Exception::InvalidMoneyString do
|
34
|
+
"Σ424,000".money
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_invalid_format
|
39
|
+
assert_raise ::Currency::Exception::InvalidMoneyString do
|
40
|
+
"17 dollars".money
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_does_not_truncate_precision
|
45
|
+
assert_equal "$425.002", "425.002".money.to_s
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_pads_precision
|
49
|
+
assert_equal "$0.50", "0.5".money.to_s
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
data/test/test_base.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# Copyright (C) 2006-2007 Kurt Stephens <ruby-currency(at)umleta.com>
|
2
|
+
# See LICENSE.txt for details.
|
3
|
+
|
4
|
+
# Base Test class
|
5
|
+
|
6
|
+
require 'test/unit'
|
7
|
+
require 'currency'
|
8
|
+
require 'currency/exchange/rate/source/test'
|
9
|
+
|
10
|
+
module Currency
|
11
|
+
|
12
|
+
class TestBase < Test::Unit::TestCase
|
13
|
+
def setup
|
14
|
+
super
|
15
|
+
@rate_source ||= get_rate_source
|
16
|
+
Exchange::Rate::Source.default = @rate_source
|
17
|
+
|
18
|
+
# Force non-historical money values.
|
19
|
+
Money.default_time = nil
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
def get_rate_source
|
24
|
+
source = Exchange::Rate::Source::Test.instance
|
25
|
+
deriver = Exchange::Rate::Deriver.new(:source => source)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Avoid "No test were specified" error.
|
29
|
+
def test_foo
|
30
|
+
assert true
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
# Helpers.
|
35
|
+
def assert_equal_float(x, y, eps = 1.0e-8)
|
36
|
+
d = (x * eps).abs
|
37
|
+
assert((x - d) <= y)
|
38
|
+
assert(y <= (x + d))
|
39
|
+
end
|
40
|
+
|
41
|
+
end # class
|
42
|
+
|
43
|
+
end # module
|
44
|
+
|