currency 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|