currency 0.3.3 → 0.4.0
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/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
|
##################################################
|