currency 0.3.3 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/COPYING.txt +339 -0
- data/LICENSE.txt +62 -0
- data/Manifest.txt +37 -14
- data/README.txt +8 -0
- data/Rakefile +42 -8
- data/Releases.txt +26 -0
- data/TODO.txt +1 -0
- data/examples/ex1.rb +3 -3
- data/examples/xe1.rb +3 -2
- data/lib/currency.rb +71 -9
- data/lib/currency/active_record.rb +138 -21
- data/lib/currency/core_extensions.rb +7 -5
- data/lib/currency/currency.rb +94 -177
- data/lib/currency/{currency_factory.rb → currency/factory.rb} +46 -25
- data/lib/currency/currency_version.rb +3 -3
- data/lib/currency/exception.rb +14 -14
- data/lib/currency/exchange.rb +14 -12
- data/lib/currency/exchange/rate.rb +159 -28
- data/lib/currency/exchange/rate/deriver.rb +146 -0
- data/lib/currency/exchange/rate/source.rb +84 -0
- data/lib/currency/exchange/rate/source/base.rb +156 -0
- data/lib/currency/exchange/rate/source/failover.rb +57 -0
- data/lib/currency/exchange/rate/source/historical.rb +79 -0
- data/lib/currency/exchange/rate/source/historical/rate.rb +181 -0
- data/lib/currency/exchange/rate/source/historical/writer.rb +203 -0
- data/lib/currency/exchange/rate/source/new_york_fed.rb +91 -0
- data/lib/currency/exchange/rate/source/provider.rb +105 -0
- data/lib/currency/exchange/rate/source/test.rb +50 -0
- data/lib/currency/exchange/rate/source/the_financials.rb +190 -0
- data/lib/currency/exchange/rate/source/timed_cache.rb +144 -0
- data/lib/currency/exchange/rate/source/xe.rb +166 -0
- data/lib/currency/exchange/time_quantitizer.rb +111 -0
- data/lib/currency/formatter.rb +159 -0
- data/lib/currency/macro.rb +321 -0
- data/lib/currency/money.rb +90 -64
- data/lib/currency/money_helper.rb +6 -5
- data/lib/currency/parser.rb +153 -0
- data/test/ar_column_test.rb +6 -3
- data/test/ar_simple_test.rb +5 -2
- data/test/ar_test_base.rb +39 -33
- data/test/ar_test_core.rb +64 -0
- data/test/formatter_test.rb +81 -0
- data/test/historical_writer_test.rb +184 -0
- data/test/macro_test.rb +109 -0
- data/test/money_test.rb +72 -4
- data/test/new_york_fed_test.rb +57 -0
- data/test/parser_test.rb +60 -0
- data/test/test_base.rb +13 -3
- data/test/time_quantitizer_test.rb +136 -0
- data/test/xe_test.rb +29 -5
- metadata +41 -18
- data/lib/currency/exchange/base.rb +0 -84
- data/lib/currency/exchange/test.rb +0 -39
- data/lib/currency/exchange/xe.rb +0 -250
data/lib/currency/money.rb
CHANGED
@@ -1,40 +1,39 @@
|
|
1
|
-
#
|
2
|
-
#
|
3
|
-
|
1
|
+
# Copyright (C) 2006-2007 Kurt Stephens <ruby-currency(at)umleta.com>
|
2
|
+
# See LICENSE.txt for details.
|
3
|
+
|
4
4
|
#
|
5
5
|
# Represents an amount of money in a particular currency.
|
6
6
|
#
|
7
7
|
# A Money object stores its value using a scaled Integer representation
|
8
8
|
# and a Currency object.
|
9
9
|
#
|
10
|
-
#
|
11
|
-
#
|
10
|
+
# A Money object also has a time, which is used in conversions
|
11
|
+
# against historical exchange rates.
|
12
12
|
#
|
13
|
-
|
14
|
-
module Currency
|
15
|
-
|
16
|
-
# Use this function instead of Money#new:
|
17
|
-
#
|
18
|
-
# Currency::Money("12.34", :CAD)
|
19
|
-
#
|
20
|
-
# not
|
21
|
-
#
|
22
|
-
# Currency::Money.new("12.34", :CAD)
|
23
|
-
#
|
24
|
-
# See Money#new.
|
25
|
-
def self.Money(*opts)
|
26
|
-
Money.new(*opts)
|
27
|
-
end
|
28
|
-
|
29
|
-
class Money
|
13
|
+
class Currency::Money
|
30
14
|
include Comparable
|
31
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.
|
32
31
|
#
|
33
32
|
# Construct a Money value object
|
34
33
|
# from a pre-scaled external representation:
|
35
34
|
# where x is a Float, Integer, String, etc.
|
36
35
|
#
|
37
|
-
# If a currency is not specified, Currency
|
36
|
+
# If a currency is not specified, Currency.default is used.
|
38
37
|
#
|
39
38
|
# x.Money_rep(currency)
|
40
39
|
#
|
@@ -47,63 +46,83 @@ module Currency
|
|
47
46
|
# Because the USD Currency object has a #scale of 100
|
48
47
|
#
|
49
48
|
# See #Money_rep(currency) mixin.
|
50
|
-
# See Currency#Money() function.
|
51
49
|
#
|
52
|
-
def initialize(x, currency = nil)
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
@
|
59
|
-
@
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
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)
|
65
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
|
66
78
|
end
|
67
79
|
|
68
80
|
# Compatibility with Money package.
|
69
81
|
def self.us_dollar(x)
|
70
82
|
self.new(x, :USD)
|
71
83
|
end
|
84
|
+
|
85
|
+
|
72
86
|
# Compatibility with Money package.
|
73
87
|
def cents
|
74
88
|
@rep
|
75
89
|
end
|
76
90
|
|
77
91
|
|
78
|
-
# Construct from post-scaled internal representation
|
79
|
-
def self.new_rep(r, currency = nil)
|
80
|
-
x = self.new(0, currency)
|
92
|
+
# Construct from post-scaled internal representation.
|
93
|
+
def self.new_rep(r, currency = nil, time = nil)
|
94
|
+
x = self.new(0, currency, time)
|
81
95
|
x.set_rep(r)
|
82
96
|
x
|
83
97
|
end
|
84
|
-
|
85
|
-
|
86
|
-
#
|
98
|
+
|
99
|
+
|
100
|
+
# Construct from post-scaled internal representation.
|
101
|
+
# using the same currency:
|
102
|
+
#
|
103
|
+
# x = Currency.Money("1.98", :USD)
|
87
104
|
# x.new_rep(100) => "$1.00 USD"
|
88
105
|
#
|
89
|
-
def new_rep(r)
|
90
|
-
|
106
|
+
def new_rep(r, time = nil)
|
107
|
+
time ||= @time
|
108
|
+
x = self.class.new(0, @currency, time)
|
91
109
|
x.set_rep(r)
|
92
110
|
x
|
93
111
|
end
|
94
112
|
|
95
|
-
# Do not call this method directly
|
113
|
+
# Do not call this method directly.
|
96
114
|
# CLIENTS SHOULD NEVER CALL set_rep DIRECTLY.
|
97
115
|
# You have been warned in ALL CAPS.
|
98
|
-
def set_rep(r)
|
116
|
+
def set_rep(r) # :nodoc:
|
99
117
|
r = r.to_i unless r.kind_of?(Integer)
|
100
118
|
@rep = r
|
101
119
|
end
|
102
120
|
|
103
|
-
#
|
104
|
-
|
105
|
-
|
106
|
-
|
121
|
+
# Do not call this method directly.
|
122
|
+
# CLIENTS SHOULD NEVER CALL set_time DIRECTLY.
|
123
|
+
# You have been warned in ALL CAPS.
|
124
|
+
def set_time(time) # :nodoc:
|
125
|
+
@time = time
|
107
126
|
end
|
108
127
|
|
109
128
|
# Returns the Money representation (usually an Integer).
|
@@ -116,25 +135,33 @@ module Currency
|
|
116
135
|
@currency
|
117
136
|
end
|
118
137
|
|
138
|
+
# Get the Money's time.
|
139
|
+
def time
|
140
|
+
@time
|
141
|
+
end
|
142
|
+
|
119
143
|
# Convert Money to another Currency.
|
120
144
|
# currency can be a Symbol or a Currency object.
|
121
145
|
# If currency is nil, the Currency.default is used.
|
122
|
-
def convert(currency)
|
123
|
-
currency = Currency.default if currency.nil?
|
124
|
-
currency = Currency.get(currency) unless currency.kind_of?(Currency)
|
146
|
+
def convert(currency, time = nil)
|
147
|
+
currency = ::Currency::Currency.default if currency.nil?
|
148
|
+
currency = ::Currency::Currency.get(currency) unless currency.kind_of?(Currency)
|
125
149
|
if @currency == currency
|
126
150
|
self
|
127
151
|
else
|
128
|
-
|
152
|
+
time = self.time if time == :money
|
153
|
+
Exchange::Rate::Source.current.convert(self, currency, time)
|
129
154
|
end
|
130
155
|
end
|
131
156
|
|
157
|
+
|
132
158
|
# Hash for hash table: both value and currency.
|
133
159
|
# See #eql? below.
|
134
160
|
def hash
|
135
161
|
@rep.hash ^ @currency.hash
|
136
162
|
end
|
137
163
|
|
164
|
+
|
138
165
|
# True if money values have the same value and currency.
|
139
166
|
def eql?(x)
|
140
167
|
self.class == x.class &&
|
@@ -155,7 +182,7 @@ module Currency
|
|
155
182
|
if @currency == x.currency
|
156
183
|
@rep <=> x.rep
|
157
184
|
else
|
158
|
-
@rep <=> convert(@currency).rep
|
185
|
+
@rep <=> convert(@currency, @time).rep
|
159
186
|
end
|
160
187
|
end
|
161
188
|
|
@@ -188,7 +215,7 @@ module Currency
|
|
188
215
|
new_rep(@rep * x)
|
189
216
|
end
|
190
217
|
|
191
|
-
# Money / Money =>
|
218
|
+
# Money / Money => Float (ratio)
|
192
219
|
# Money / Number => Money
|
193
220
|
#
|
194
221
|
# Right side must be Money or Number.
|
@@ -202,7 +229,7 @@ module Currency
|
|
202
229
|
end
|
203
230
|
end
|
204
231
|
|
205
|
-
# Formats the Money value as a String.
|
232
|
+
# Formats the Money value as a String using the Currency's Formatter.
|
206
233
|
def format(*opt)
|
207
234
|
@currency.format(self, *opt)
|
208
235
|
end
|
@@ -240,10 +267,10 @@ module Currency
|
|
240
267
|
end
|
241
268
|
|
242
269
|
# Returns the Money's value representation in another currency.
|
243
|
-
def Money_rep(currency)
|
270
|
+
def Money_rep(currency, time = nil)
|
244
271
|
# Attempt conversion?
|
245
|
-
if @currency != currency
|
246
|
-
self.convert(currency).rep
|
272
|
+
if @currency != currency || (time && @time != time)
|
273
|
+
self.convert(currency, time).rep
|
247
274
|
# raise("Incompatible Currency: #{@currency} != #{currency}")
|
248
275
|
else
|
249
276
|
@rep
|
@@ -253,7 +280,7 @@ module Currency
|
|
253
280
|
# Basic inspection, with symbol and currency code.
|
254
281
|
# The standard #inspect method is available as #inspect_deep.
|
255
282
|
def inspect(*opts)
|
256
|
-
self.format(:
|
283
|
+
self.format(:symbol => true, :code => true)
|
257
284
|
end
|
258
285
|
|
259
286
|
# How to alias a method defined in an object superclass in a different class:
|
@@ -263,6 +290,5 @@ module Currency
|
|
263
290
|
# self.class.superclass.instance_method(:inspect).bind(self).call
|
264
291
|
# end
|
265
292
|
|
266
|
-
|
293
|
+
end # class
|
267
294
|
|
268
|
-
end # module
|
@@ -1,12 +1,13 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
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.
|
4
6
|
def money_field(object, method, options = {})
|
5
7
|
InstanceTag.new(object, method, self).to_input_field_tag("text", options)
|
6
8
|
end
|
7
|
-
end
|
8
|
-
end
|
9
9
|
end
|
10
10
|
|
11
11
|
|
12
12
|
ActionView::Base.load_helper(File.dirname(__FILE__))
|
13
|
+
|
@@ -0,0 +1,153 @@
|
|
1
|
+
# Copyright (C) 2006-2007 Kurt Stephens <ruby-currency(at)umleta.com>
|
2
|
+
# See LICENSE.txt for details.
|
3
|
+
|
4
|
+
|
5
|
+
# This class parses a Money value from a String.
|
6
|
+
# Each Currency has a default Parser.
|
7
|
+
class Currency::Parser
|
8
|
+
|
9
|
+
# The default Currency to use if no Currency is specified.
|
10
|
+
attr_accessor :currency
|
11
|
+
|
12
|
+
# If true and a parsed string contains a ISO currency code
|
13
|
+
# that is not the same as currency,
|
14
|
+
# #parse() will raise IncompatibleCurrency.
|
15
|
+
# Defaults to false.
|
16
|
+
attr_accessor :enforce_currency
|
17
|
+
|
18
|
+
# The default Time to use if no Time is specified in the string.
|
19
|
+
attr_accessor :time
|
20
|
+
|
21
|
+
@@default = nil
|
22
|
+
# Get the default Formatter.
|
23
|
+
def self.default
|
24
|
+
@@default || self.new
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
# Set the default Formatter.
|
29
|
+
def self.default=(x)
|
30
|
+
@@default = x
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
def initialize(opt = { })
|
35
|
+
opt.each_pair{ | k, v | self.send("#{k}=", v) }
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
def _parse(str) # :nodoc:
|
40
|
+
x = str
|
41
|
+
|
42
|
+
# Get currency.
|
43
|
+
# puts "str = #{str.inspect}, @currency = #{@currency}"
|
44
|
+
|
45
|
+
md = nil # match data
|
46
|
+
|
47
|
+
# $stderr.puts "'#{x}'.Money_rep(#{self})"
|
48
|
+
|
49
|
+
# $stderr.puts "x = #{x}"
|
50
|
+
convert_currency = nil
|
51
|
+
# Handle currency code at front of string.
|
52
|
+
if (md = /([A-Z][A-Z][A-Z])/.match(x))
|
53
|
+
curr = ::Currency::Currency.get(md[1])
|
54
|
+
x = md.pre_match + md.post_match
|
55
|
+
if @currency && @currency != curr
|
56
|
+
if @enforce_currency
|
57
|
+
raise ::Currency::Exception::IncompatibleCurrency.new("#{str.inspect} #{@currency.code}")
|
58
|
+
end
|
59
|
+
convert_currency = @currency
|
60
|
+
end
|
61
|
+
currency = curr
|
62
|
+
else
|
63
|
+
currency = @currency || ::Currency::Currency.default
|
64
|
+
currency = ::Currency::Currency.get(currency)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Remove placeholders and symbol.
|
68
|
+
x = x.gsub(/[, ]/, '')
|
69
|
+
symbol = currency.symbol # FIXME
|
70
|
+
x = x.gsub(symbol, '') if symbol
|
71
|
+
|
72
|
+
# $stderr.puts "x = #{x.inspect}"
|
73
|
+
# Match: whole Currency value.
|
74
|
+
if md = /^([-+]?\d+)\.?$/.match(x)
|
75
|
+
# $stderr.puts "'#{self}'.parse(#{str}) => EXACT"
|
76
|
+
x = ::Currency::Money.new_rep(md[1].to_i * currency.scale, currency, @time)
|
77
|
+
|
78
|
+
# Match: fractional Currency value.
|
79
|
+
elsif md = /^([-+]?)(\d*)\.(\d+)$/.match(x)
|
80
|
+
sign = md[1]
|
81
|
+
whole = md[2]
|
82
|
+
part = md[3]
|
83
|
+
|
84
|
+
# $stderr.puts "'#{self}'.parse(#{str}) => DECIMAL (#{sign} #{whole} . #{part})"
|
85
|
+
|
86
|
+
if part.length != currency.scale
|
87
|
+
|
88
|
+
# Pad decimal places with additional '0'
|
89
|
+
while part.length < currency.scale_exp
|
90
|
+
part << '0'
|
91
|
+
end
|
92
|
+
|
93
|
+
# Truncate to Currency's decimal size.
|
94
|
+
part = part[0 ... currency.scale_exp]
|
95
|
+
|
96
|
+
# $stderr.puts " => INEXACT DECIMAL '#{whole}'"
|
97
|
+
end
|
98
|
+
|
99
|
+
# Put the string back together:
|
100
|
+
# #{sign}#{whole}#{part}
|
101
|
+
whole = sign + whole + part
|
102
|
+
# $stderr.puts " => REP = #{whole}"
|
103
|
+
|
104
|
+
x = whole.to_i
|
105
|
+
|
106
|
+
x = ::Currency::Money.new_rep(x, currency, @time)
|
107
|
+
else
|
108
|
+
# $stderr.puts "'#{self}'.parse(#{str}) => ??? '#{x}'"
|
109
|
+
#x.to_f.Money_rep(self)
|
110
|
+
raise ::Currency::Exception::InvalidMoneyString.new("#{str.inspect} #{currency} #{x.inspect}")
|
111
|
+
end
|
112
|
+
|
113
|
+
# Do conversion.
|
114
|
+
if convert_currency
|
115
|
+
x = x.convert(convert_currency)
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
x
|
120
|
+
end
|
121
|
+
|
122
|
+
|
123
|
+
@@empty_hash = { }
|
124
|
+
@@empty_hash.freeze
|
125
|
+
|
126
|
+
# Parse a Money string in this Currency.
|
127
|
+
#
|
128
|
+
# "123.45".money # Using default Currency.
|
129
|
+
# => $123.45 USD
|
130
|
+
#
|
131
|
+
# "123.45 USD".money # Explicit Currency.
|
132
|
+
# => $123.45 USD
|
133
|
+
#
|
134
|
+
# "123.45 CAD".money
|
135
|
+
# => $123.45 CAD
|
136
|
+
#
|
137
|
+
# "123.45 CAD".money(:USD) # Incompatible explicit Currency.
|
138
|
+
# !!! "123.45 CAD" USD (Currency::Exception::IncompatibleCurrency)
|
139
|
+
#
|
140
|
+
def parse(str, opt = @@empty_hash)
|
141
|
+
prs = self
|
142
|
+
|
143
|
+
unless opt.empty?
|
144
|
+
prs = prs.clone
|
145
|
+
opt.each_pair{ | k, v | prs.send("#{k}=", v) }
|
146
|
+
end
|
147
|
+
|
148
|
+
prs._parse(str)
|
149
|
+
end
|
150
|
+
|
151
|
+
end # class
|
152
|
+
|
153
|
+
|
data/test/ar_column_test.rb
CHANGED
@@ -1,4 +1,7 @@
|
|
1
|
-
|
1
|
+
# Copyright (C) 2006-2007 Kurt Stephens <ruby-currency(at)umleta.com>
|
2
|
+
# See LICENSE.txt for details.
|
3
|
+
|
4
|
+
require 'test/ar_test_core'
|
2
5
|
require 'currency'
|
3
6
|
|
4
7
|
require 'rubygems'
|
@@ -8,7 +11,7 @@ require 'currency/active_record'
|
|
8
11
|
|
9
12
|
module Currency
|
10
13
|
|
11
|
-
class ArFieldTest <
|
14
|
+
class ArFieldTest < ArTestCore
|
12
15
|
|
13
16
|
##################################################
|
14
17
|
# Basic CurrenyTest AR::B class
|
@@ -32,7 +35,7 @@ class ArFieldTest < ArTestBase
|
|
32
35
|
|
33
36
|
class CurrencyColumnTest < AR_B
|
34
37
|
set_table_name TABLE_NAME
|
35
|
-
|
38
|
+
attr_money :amount, :currency_column => true
|
36
39
|
end
|
37
40
|
|
38
41
|
##################################################
|