currency 0.1.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.tar.gz.sig +1 -0
- data/ChangeLog +5 -0
- data/README +33 -0
- data/Rakefile +349 -0
- data/Releases +6 -0
- data/TODO +0 -0
- data/examples/ex1.rb +13 -0
- data/examples/xe1.rb +19 -0
- data/lib/currency.rb +13 -0
- data/lib/currency/active_record.rb +70 -0
- data/lib/currency/core_extensions.rb +25 -0
- data/lib/currency/currency.rb +203 -0
- data/lib/currency/currency_exchange.rb +50 -0
- data/lib/currency/currency_exchange_test.rb +27 -0
- data/lib/currency/currency_exchange_xe.rb +140 -0
- data/lib/currency/currency_factory.rb +73 -0
- data/lib/currency/currency_version.rb +6 -0
- data/lib/currency/exception.rb +10 -0
- data/lib/currency/exchange_rate.rb +47 -0
- data/lib/currency/money.rb +190 -0
- data/lib/currency/money_helper.rb +12 -0
- data/scripts/gemdoc.rb +62 -0
- data/test/money_test.rb +166 -0
- data/test/test_base.rb +31 -0
- data/test/xe_test.rb +33 -0
- metadata +90 -0
- metadata.gz.sig +0 -0
@@ -0,0 +1,73 @@
|
|
1
|
+
module Currency
|
2
|
+
class CurrencyFactory
|
3
|
+
# Default factory.
|
4
|
+
@@default = nil
|
5
|
+
def self.default
|
6
|
+
@@default ||= CurrencyFactory.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@currency_by_code = { }
|
11
|
+
@currency_by_symbol = { }
|
12
|
+
@currency = nil
|
13
|
+
@USD = nil
|
14
|
+
@CAD = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
# Lookup table by code
|
18
|
+
def get_by_code(x)
|
19
|
+
x = Currency.cast_code(x)
|
20
|
+
# $stderr.puts "get_by_code(#{x})"
|
21
|
+
@currency_by_code[x] ||= load(Currency.new(x))
|
22
|
+
end
|
23
|
+
|
24
|
+
# Lookup table by symbol
|
25
|
+
def get_by_symbol(symbol)
|
26
|
+
@currency_by_symbol[symbol] ||= load(Currency.new(nil, symbol))
|
27
|
+
end
|
28
|
+
|
29
|
+
def load(currency)
|
30
|
+
# $stderr.puts "BEFORE: load(#{currency.code})"
|
31
|
+
|
32
|
+
# SAMPLE CODE
|
33
|
+
if currency.code == :USD || currency.symbol == '$'
|
34
|
+
# $stderr.puts "load('USD')"
|
35
|
+
currency.code= :USD
|
36
|
+
currency.symbol= '$'
|
37
|
+
currency.scale= 100
|
38
|
+
elsif currency.code == :CAD
|
39
|
+
# $stderr.puts "load('CAD')"
|
40
|
+
currency.symbol= '$'
|
41
|
+
currency.scale= 100
|
42
|
+
else
|
43
|
+
currency.symbol= nil
|
44
|
+
currency.scale= 100
|
45
|
+
end
|
46
|
+
|
47
|
+
# $stderr.puts "AFTER: load(#{currency.inspect})"
|
48
|
+
|
49
|
+
install(currency)
|
50
|
+
end
|
51
|
+
|
52
|
+
def install(currency)
|
53
|
+
@currency_by_symbol[currency.symbol] ||= currency unless currency.symbol.nil?
|
54
|
+
@currency_by_code[currency.code] = currency
|
55
|
+
end
|
56
|
+
|
57
|
+
# Standard Currencys
|
58
|
+
def USD
|
59
|
+
@USD ||= self.get_by_code(:USD)
|
60
|
+
end
|
61
|
+
|
62
|
+
def CAD
|
63
|
+
@CAD ||= self.get_by_code(:CAD)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Default Currency
|
67
|
+
def currency
|
68
|
+
@currency ||= self.USD
|
69
|
+
end
|
70
|
+
end
|
71
|
+
# END MODULE
|
72
|
+
end
|
73
|
+
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Currency
|
2
|
+
# Represents a convertion rate between two currencies
|
3
|
+
class ExchangeRate
|
4
|
+
def initialize(c1, c2, c1_to_c2_rate, source = "UNKNOWN", date = nil, recip = true)
|
5
|
+
@c1 = c1
|
6
|
+
@c2 = c2
|
7
|
+
@rate = c1_to_c2_rate
|
8
|
+
@source = source
|
9
|
+
@date = date || Time.now
|
10
|
+
@reciprocal = recip
|
11
|
+
end
|
12
|
+
|
13
|
+
def c1
|
14
|
+
@c1
|
15
|
+
end
|
16
|
+
|
17
|
+
def c2
|
18
|
+
@c2
|
19
|
+
end
|
20
|
+
|
21
|
+
def rate
|
22
|
+
@rate
|
23
|
+
end
|
24
|
+
|
25
|
+
def source
|
26
|
+
@source
|
27
|
+
end
|
28
|
+
|
29
|
+
def date
|
30
|
+
@date
|
31
|
+
end
|
32
|
+
|
33
|
+
def convert(m, c1)
|
34
|
+
m = m.to_f
|
35
|
+
if ( @c1 == c1 )
|
36
|
+
# $stderr.puts "Converting #{@c1} #{m} to #{@c2} #{m * @rate} using #{@rate}"
|
37
|
+
m * @rate
|
38
|
+
else
|
39
|
+
# $stderr.puts "Converting #{@c2} #{m} to #{@c1} #{m / @rate} using #{1.0 / @rate}; recip"
|
40
|
+
m / @rate
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# END MODULE
|
46
|
+
end
|
47
|
+
|
@@ -0,0 +1,190 @@
|
|
1
|
+
module Currency
|
2
|
+
|
3
|
+
# Represents an amount of money in a particular currency.
|
4
|
+
#
|
5
|
+
# NOTE: do we need to store a time, so we can use
|
6
|
+
# historical FX rates to convert?
|
7
|
+
#
|
8
|
+
class Money
|
9
|
+
include Comparable
|
10
|
+
|
11
|
+
|
12
|
+
# Construct from a pre-scaled external representation:
|
13
|
+
# Float, Integer, String, etc.
|
14
|
+
# See #Money_rep(currency) mixin.
|
15
|
+
def initialize(x, currency = nil)
|
16
|
+
# Xform currency
|
17
|
+
currency = Currency.default if currency.nil?
|
18
|
+
currency = Currency.get(currency) unless currency.kind_of?(Currency)
|
19
|
+
|
20
|
+
# Set ivars
|
21
|
+
@currency = currency;
|
22
|
+
@rep = x.Money_rep(@currency)
|
23
|
+
|
24
|
+
# Handle conversion of "USD 123.45"
|
25
|
+
if @rep.kind_of?(Money)
|
26
|
+
@currency = @rep.currency
|
27
|
+
@rep = @rep.rep
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Compatibility with Money package.
|
32
|
+
def self.us_dollar(x)
|
33
|
+
self.new(x, :USD)
|
34
|
+
end
|
35
|
+
def cents
|
36
|
+
@rep
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
# Construct from post-scaled internal representation
|
41
|
+
def self.new_rep(r, currency = nil)
|
42
|
+
x = self.new(0, currency)
|
43
|
+
x.set_rep(r)
|
44
|
+
x
|
45
|
+
end
|
46
|
+
def new_rep(r)
|
47
|
+
x = self.class.new(0, @currency)
|
48
|
+
x.set_rep(r)
|
49
|
+
x
|
50
|
+
end
|
51
|
+
|
52
|
+
# CLIENTS SHOULD NEVER CALL set_rep DIRECTLY!
|
53
|
+
def set_rep(r)
|
54
|
+
r = r.to_i unless r.kind_of?(Integer)
|
55
|
+
@rep = r
|
56
|
+
end
|
57
|
+
|
58
|
+
def Money_rep(currency)
|
59
|
+
$stderr.puts "@currency != currency (#{@currency.inspect} != #{currency.inspect}" unless @currency == currency
|
60
|
+
@rep
|
61
|
+
end
|
62
|
+
|
63
|
+
def rep
|
64
|
+
@rep
|
65
|
+
end
|
66
|
+
|
67
|
+
# Get the money's Currency
|
68
|
+
def currency
|
69
|
+
@currency
|
70
|
+
end
|
71
|
+
|
72
|
+
# Convert Money to another Currency
|
73
|
+
def convert(currency)
|
74
|
+
currency = Currency.default if currency.nil?
|
75
|
+
currency = Currency.get(currency) unless currency.kind_of?(Currency)
|
76
|
+
if @currency == currency
|
77
|
+
self
|
78
|
+
else
|
79
|
+
CurrencyExchange.default.convert(self, currency)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Relational operations on Money values.
|
84
|
+
def eql?(x)
|
85
|
+
@rep == x.rep && @currency == x.currency
|
86
|
+
end
|
87
|
+
|
88
|
+
def ==(x)
|
89
|
+
@rep == x.rep && @currency == x.currency
|
90
|
+
end
|
91
|
+
|
92
|
+
def <=>(x)
|
93
|
+
if @currency == x.currency
|
94
|
+
@rep <=> x.rep
|
95
|
+
else
|
96
|
+
@rep <=> convert(@currency).rep
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Operations on Money values.
|
101
|
+
|
102
|
+
|
103
|
+
def -@
|
104
|
+
# - Money => Money
|
105
|
+
new_rep(- @rep)
|
106
|
+
end
|
107
|
+
|
108
|
+
# Right side maybe coerced to Money.
|
109
|
+
def +(x)
|
110
|
+
# Money + (Number|Money) => Money
|
111
|
+
new_rep(@rep + x.Money_rep(@currency))
|
112
|
+
end
|
113
|
+
|
114
|
+
# Right side maybe coerced to Money.
|
115
|
+
def -(x)
|
116
|
+
# Money - (Number|Money) => Money
|
117
|
+
new_rep(@rep - x.Money_rep(@currency))
|
118
|
+
end
|
119
|
+
|
120
|
+
# Right side must be number
|
121
|
+
def *(x)
|
122
|
+
# Money * Number => Money
|
123
|
+
new_rep(@rep * x)
|
124
|
+
end
|
125
|
+
|
126
|
+
# Right side must be number or Money
|
127
|
+
def /(x)
|
128
|
+
if x.kind_of?(self.class)
|
129
|
+
# Money / Money => ratio
|
130
|
+
(@rep.to_f) / (x.Money_rep(@currency).to_f)
|
131
|
+
else
|
132
|
+
# Money / Number => Money
|
133
|
+
new_rep(@rep / x)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def format(*opt)
|
138
|
+
@currency.format(self, *opt)
|
139
|
+
end
|
140
|
+
|
141
|
+
# Coercions
|
142
|
+
def to_s(*opt)
|
143
|
+
@currency.format(self, *opt)
|
144
|
+
end
|
145
|
+
|
146
|
+
def to_f
|
147
|
+
Float(@rep) / @currency.scale
|
148
|
+
end
|
149
|
+
|
150
|
+
def to_i
|
151
|
+
@rep / @currency.scale
|
152
|
+
end
|
153
|
+
|
154
|
+
def zero?
|
155
|
+
@rep == 0
|
156
|
+
end
|
157
|
+
|
158
|
+
def positive?
|
159
|
+
@rep > 0
|
160
|
+
end
|
161
|
+
|
162
|
+
def negative?
|
163
|
+
@rep < 0
|
164
|
+
end
|
165
|
+
|
166
|
+
# Implicit currency conversions?
|
167
|
+
def Money_rep(currency)
|
168
|
+
# Attempt conversion?
|
169
|
+
if @currency != currency
|
170
|
+
self.convert(currency).rep
|
171
|
+
# raise("Incompatible Currency: #{@currency} != #{currency}")
|
172
|
+
else
|
173
|
+
@rep
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def inspect(*opts)
|
178
|
+
self.format(:with_symbol, :with_currency).inspect
|
179
|
+
end
|
180
|
+
|
181
|
+
# How to alias a method defined in an object superclass in a different class:
|
182
|
+
define_method(:inspect_deep, Object.instance_method(:inspect))
|
183
|
+
# How call a method defined in a superclass from a method with a different name:
|
184
|
+
#def inspect_deep(*opts)
|
185
|
+
# self.class.superclass.instance_method(:inspect).bind(self).call
|
186
|
+
#end
|
187
|
+
end
|
188
|
+
|
189
|
+
# END MODULE
|
190
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module ActionView
|
2
|
+
module Helpers
|
3
|
+
module MoneyHelper
|
4
|
+
def money_field(object, method, options = {})
|
5
|
+
InstanceTag.new(object, method, self).to_input_field_tag("text", options)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
ActionView::Base.load_helper(File.dirname(__FILE__))
|
data/scripts/gemdoc.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
Gem.manage_gems
|
5
|
+
require 'rubygems/user_interaction'
|
6
|
+
|
7
|
+
include Gem::DefaultUserInteraction
|
8
|
+
|
9
|
+
$gm = Gem::CommandManager.instance
|
10
|
+
|
11
|
+
class CaptureSay
|
12
|
+
attr_reader :string
|
13
|
+
def initialize
|
14
|
+
@string = ''
|
15
|
+
end
|
16
|
+
def say(msg)
|
17
|
+
@string << msg << "\n"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def pre(cmd, opts)
|
22
|
+
puts "<pre>"
|
23
|
+
cmd.invoke opts
|
24
|
+
puts "</pre>"
|
25
|
+
end
|
26
|
+
|
27
|
+
def table_of_contents
|
28
|
+
cs = CaptureSay.new
|
29
|
+
use_ui(cs) do
|
30
|
+
$gm['help'].invoke 'commands'
|
31
|
+
end
|
32
|
+
# We're only interested in the lines that actually describe a command.
|
33
|
+
out = cs.string.grep(/^\s+(\w+)\s+(.*)$/).join("\n")
|
34
|
+
# Add a link to the relevant section in the margin.
|
35
|
+
out.gsub(/^\s+(\w+)/) {
|
36
|
+
cmd_name = $1
|
37
|
+
" [http://currency.rubyforge.org/wiki/wiki.pl?CurrencyReference##{cmd_name} -] #{cmd_name}"
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
while line = gets
|
42
|
+
if line =~ /^!/
|
43
|
+
cmd, arg = line.split
|
44
|
+
case cmd
|
45
|
+
when "!usage"
|
46
|
+
begin
|
47
|
+
cmdobj = $gm[arg]
|
48
|
+
pre(cmdobj, "--help")
|
49
|
+
rescue NoMethodError
|
50
|
+
puts "Usage of command #{arg} failed"
|
51
|
+
end
|
52
|
+
when "!toc"
|
53
|
+
puts table_of_contents()
|
54
|
+
when "!toc-link"
|
55
|
+
puts "\"Table of Contents\":http://currency.rubyforge.org/read/chapter/10#toc"
|
56
|
+
when "!version"
|
57
|
+
puts Gem::RubyGemsPackageVersion
|
58
|
+
end
|
59
|
+
else
|
60
|
+
puts line
|
61
|
+
end
|
62
|
+
end
|
data/test/money_test.rb
ADDED
@@ -0,0 +1,166 @@
|
|
1
|
+
# REAL
|
2
|
+
#require File.dirname(__FILE__) + '/../test_helper'
|
3
|
+
|
4
|
+
require 'test/test_base'
|
5
|
+
require 'currency' # For :type => :money
|
6
|
+
|
7
|
+
module Currency
|
8
|
+
|
9
|
+
class MoneyTest < TestBase
|
10
|
+
def setup
|
11
|
+
super
|
12
|
+
end
|
13
|
+
|
14
|
+
############################################
|
15
|
+
# Simple stuff.
|
16
|
+
#
|
17
|
+
|
18
|
+
def test_create
|
19
|
+
assert_kind_of Money, m = Money.
|
20
|
+
new(1.99)
|
21
|
+
|
22
|
+
m
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_zero
|
26
|
+
m = Money.new(0)
|
27
|
+
assert ! m.negative?
|
28
|
+
assert m.zero?
|
29
|
+
assert ! m.positive?
|
30
|
+
m
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_negative
|
34
|
+
m = Money.new(-1.00, :USD)
|
35
|
+
assert m.negative?
|
36
|
+
assert ! m.zero?
|
37
|
+
assert ! m.positive?
|
38
|
+
m
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_positive
|
42
|
+
m = Money.new(2.99, :USD)
|
43
|
+
assert ! m.negative?
|
44
|
+
assert ! m.zero?
|
45
|
+
assert m.positive?
|
46
|
+
m
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_relational
|
50
|
+
n = test_negative
|
51
|
+
z = test_zero
|
52
|
+
p = test_positive
|
53
|
+
|
54
|
+
assert (n < p)
|
55
|
+
assert ! (n > p)
|
56
|
+
assert ! (p < n)
|
57
|
+
assert (p > n)
|
58
|
+
assert (p != n)
|
59
|
+
|
60
|
+
assert (z <= z)
|
61
|
+
assert (z >= z)
|
62
|
+
|
63
|
+
assert (z <= p)
|
64
|
+
assert (n <= z)
|
65
|
+
assert (z >= n)
|
66
|
+
|
67
|
+
assert n == n
|
68
|
+
assert p == p
|
69
|
+
|
70
|
+
assert z == test_zero
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_rep
|
74
|
+
assert_not_nil m = Money.new(123, :USD)
|
75
|
+
assert m.rep == 12300
|
76
|
+
|
77
|
+
assert_not_nil m = Money.new(123.45, :USD)
|
78
|
+
assert m.rep == 12345
|
79
|
+
|
80
|
+
assert_not_nil m = Money.new("123.456", :USD)
|
81
|
+
assert m.rep == 12345
|
82
|
+
end
|
83
|
+
|
84
|
+
def test_convert
|
85
|
+
assert_not_nil m = Money.new("123.456", :USD)
|
86
|
+
assert m.rep == 12345
|
87
|
+
|
88
|
+
assert_equal 123, m.to_i
|
89
|
+
assert_equal 123.45, m.to_f
|
90
|
+
assert_equal "$123.45", m.to_s
|
91
|
+
end
|
92
|
+
|
93
|
+
def test_eql
|
94
|
+
assert_not_nil usd = Money.new(123, :USD)
|
95
|
+
assert_not_nil cad = Money.new(123, :CAD)
|
96
|
+
|
97
|
+
assert_equal :USD, usd.currency.code
|
98
|
+
assert_equal :CAD, cad.currency.code
|
99
|
+
|
100
|
+
assert_equal usd.rep, cad.rep
|
101
|
+
assert usd.currency != cad.currency
|
102
|
+
|
103
|
+
assert usd != cad
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
def test_op
|
108
|
+
# Using default load_exchange_rate
|
109
|
+
assert_not_nil usd = Money.new(123.45, :USD)
|
110
|
+
assert_not_nil cad = Money.new(123.45, :CAD)
|
111
|
+
|
112
|
+
# - Money => Money
|
113
|
+
assert_equal -12345, (- usd).rep
|
114
|
+
assert_equal :USD, (- usd).currency.code
|
115
|
+
|
116
|
+
assert_equal -12345, (- cad).rep
|
117
|
+
assert_equal :CAD, (- cad).currency.code
|
118
|
+
|
119
|
+
# Money + Money => Money
|
120
|
+
assert_kind_of Money, m = (usd + usd)
|
121
|
+
assert_equal 24690, m.rep
|
122
|
+
assert_equal :USD, m.currency.code
|
123
|
+
|
124
|
+
assert_kind_of Money, m = (usd + cad)
|
125
|
+
assert_equal 22889, m.rep
|
126
|
+
assert_equal :USD, m.currency.code
|
127
|
+
|
128
|
+
assert_kind_of Money, m = (cad + usd)
|
129
|
+
assert_equal 26798, m.rep
|
130
|
+
assert_equal :CAD, m.currency.code
|
131
|
+
|
132
|
+
# Money - Money => Money
|
133
|
+
assert_kind_of Money, m = (usd - usd)
|
134
|
+
assert_equal 0, m.rep
|
135
|
+
assert_equal :USD, m.currency.code
|
136
|
+
|
137
|
+
assert_kind_of Money, m = (usd - cad)
|
138
|
+
assert_equal 1801, m.rep
|
139
|
+
assert_equal :USD, m.currency.code
|
140
|
+
|
141
|
+
assert_kind_of Money, m = (cad - usd)
|
142
|
+
assert_equal -2108, m.rep
|
143
|
+
assert_equal :CAD, m.currency.code
|
144
|
+
|
145
|
+
# Money * Numeric => Money
|
146
|
+
assert_kind_of Money, m = (usd * 0.5)
|
147
|
+
assert_equal 6172, m.rep
|
148
|
+
assert_equal :USD, m.currency.code
|
149
|
+
|
150
|
+
# Money / Numeric => Money
|
151
|
+
assert_kind_of Money, m = usd / 3
|
152
|
+
assert_equal 4115, m.rep
|
153
|
+
assert_equal :USD, m.currency.code
|
154
|
+
|
155
|
+
# Money / Money => Numeric
|
156
|
+
assert_kind_of Numeric, m = usd / Money.new("41.15", :USD)
|
157
|
+
assert_equal_float 3.0, m
|
158
|
+
|
159
|
+
assert_kind_of Numeric, m = (usd / cad)
|
160
|
+
assert_equal_float CurrencyExchangeTest.USD_CAD, m, 0.0001
|
161
|
+
end
|
162
|
+
|
163
|
+
end
|
164
|
+
|
165
|
+
end # module
|
166
|
+
|