currency 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -12,7 +12,7 @@ require 'hoe'
12
12
  PKG_Name = 'Currency'
13
13
  PKG_NAME = PKG_Name.gsub(/[a-z][A-Z]/) {|x| "#{x[0,1]}_#{x[1,1]}"}.downcase
14
14
 
15
- hoe = Hoe.new("currency", '0.1.2') do |p|
15
+ hoe = Hoe.new("currency", '0.2.0') do |p|
16
16
  p.author = 'Kurt Stephens'
17
17
  p.description = %{Currency models currencies, monetary values, foreign exchanges.}
18
18
  p.email = "ruby-#{PKG_NAME}@umleta.com"
data/lib/currency.rb CHANGED
@@ -1,16 +1,7 @@
1
1
  # -*- ruby -*-
2
-
3
- $:.unshift(File.dirname(__FILE__)) unless
4
- $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
5
-
6
- require 'currency/exception'
7
- require 'currency/money'
8
- require 'currency/currency_factory'
9
- require 'currency/currency'
10
- require 'currency/money'
11
- require 'currency/exchange'
12
- require 'currency/core_extensions'
13
-
2
+ #
3
+ # = Currency
4
+ #
14
5
  # The Currency package provides an object-oriented model of:
15
6
  #
16
7
  # * currencies
@@ -22,10 +13,12 @@ require 'currency/core_extensions'
22
13
  #
23
14
  # * Currency::Money - uses a scaled integer representation of a monetary value and performs accurate conversions to and from string values.
24
15
  # * Currency::Currency - provides an object-oriented representation of a currency.
25
- # * Currency::Exchange::Base - the base class for a "currency exchange rate" provider.
16
+ # * Currency::Exchange::Base - the base class for a currency exchange rate provider.
26
17
  # * Currency::Exchange::Rate - represents a exchange rate between two currencies.
27
18
  #
28
- # Below is a basic example:
19
+ #
20
+ # The example below uses Currency::Exchange::Xe to automatically get
21
+ # exchange rates from http://xe.com/ :
29
22
  #
30
23
  # require 'currency'
31
24
  # require 'currency/exchange/xe'
@@ -35,8 +28,6 @@ require 'currency/core_extensions'
35
28
  # cad = usd.convert(:CAD)
36
29
  # puts "cad = #{cad.format}"
37
30
  #
38
- # The example above uses Currency::Exchange::Xe automatically get exchange rates from http://xe.com/.
39
- #
40
31
  # == ActiveRecord Suppport
41
32
  #
42
33
  # This package also contains ActiveRecord support for money values:
@@ -63,7 +54,25 @@ require 'currency/core_extensions'
63
54
  #
64
55
  # == Examples
65
56
  #
66
- # * The "test programs":http://rubyforge.org/cgi-bin/viewvc.cgi/currency/trunk/examples?root=currency
67
- # * The "test cases":http://rubyforge.org/cgi-bin/viewvc.cgi/currency/trunk/test/?root=currency
57
+ # See the examples/ and test/ directorys
68
58
  #
59
+ # == Author
60
+ #
61
+ # Kurt Stephens http://kurtstephens.com
62
+ #
63
+ # == Support
64
+ #
65
+ # ruby-currency(at)umleta.com
66
+ #
67
+
68
+ $:.unshift(File.dirname(__FILE__)) unless
69
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
70
+
71
+ require 'currency/exception'
72
+ require 'currency/money'
73
+ require 'currency/currency_factory'
74
+ require 'currency/currency'
75
+ require 'currency/money'
76
+ require 'currency/exchange'
77
+ require 'currency/core_extensions'
69
78
 
@@ -9,7 +9,53 @@ module Currency
9
9
  base.extend(ClassMethods)
10
10
  end
11
11
 
12
+
13
+ # == ActiveRecord Suppport
14
+ #
15
+ # This package also contains ActiveRecord support for money values:
16
+ #
17
+ # require 'currency'
18
+ # require 'currency/active_record'
19
+ #
20
+ # class Entry < ActiveRecord::Base
21
+ # money :amount
22
+ # end
23
+ #
12
24
  module ClassMethods
25
+
26
+ # Defines a Money object attribute that is bound
27
+ # to a database column. The database column to store the
28
+ # Money value representation is assumed to be
29
+ # INTEGER.
30
+ #
31
+ # Options:
32
+ #
33
+ # :currency => :USD
34
+ #
35
+ # Defines the Currency to use for storing a normalized Money
36
+ # value. This allows SQL summary operations,
37
+ # like SUM(), MAX(), AVG(), etc., to produce meaningful results,
38
+ # regardless of the initial currency specified. If this
39
+ # option is used, subsequent reads will be in the specified
40
+ # normalization :currency. Defaults to :USD.
41
+ #
42
+ # :currency_field => undef
43
+ #
44
+ # Defines the name of the CHAR(3) column that is used to store and
45
+ # retrieve the Money's Currency code. If this option is used, each
46
+ # record may use a different Currency to store the result, such
47
+ # that SQL summary operations, like SUM(), MAX(), AVG(),
48
+ # may return meaningless results.
49
+ #
50
+ # :currency_preferred_field => undef
51
+ #
52
+ # Defines the name of a CHAR(3) column used to store and
53
+ # retrieve the Money's Currency code. This option can be used
54
+ # with normalize Money values to retrieve the Money value
55
+ # in its original Currency, while
56
+ # allowing SQL summary operations on a normalized Money value
57
+ # to still be valid.
58
+ #
13
59
  def money(attr_name, *opts)
14
60
  opts = Hash.[](*opts)
15
61
 
@@ -22,6 +68,12 @@ module Currency
22
68
  currency = "read_attribute(:#{currency_field})"
23
69
  end
24
70
 
71
+ currency_preferred_field = opts[:currency_preferred_field]
72
+ if currency_preferred_field
73
+ read_preferred_currency = "@#{attr_name} = @#{attr_name}.convert(read_attribute(:#{:currency_preferred_field}))"
74
+ write_preferred_currency = "write_attribute(:#{currency_preferred_field}, @#{attr_name}_money.currency.code)"
75
+ end
76
+
25
77
  currency ||= ':USD'
26
78
 
27
79
  validate = ''
@@ -33,7 +85,10 @@ def #{attr_name}
33
85
  # $stderr.puts " \#{self.class.name}##{attr_name}"
34
86
  unless @#{attr_name}
35
87
  #{attr_name}_rep = read_attribute(:#{attr_name})
36
- @#{attr_name} = Money.new_rep(#{attr_name}_rep, #{currency}) unless #{attr_name}_rep.nil?
88
+ unless #{attr_name}_rep.nil?
89
+ @#{attr_name} = Money.new_rep(#{attr_name}_rep, #{currency})
90
+ #{read_preferred_currency}
91
+ end
37
92
  end
38
93
  @#{attr_name}
39
94
  end
@@ -42,7 +97,9 @@ def #{attr_name}=(value)
42
97
  ;
43
98
  elsif value.kind_of?(Integer) || value.kind_of?(String) || value.kind_of?(Float)
44
99
  #{attr_name}_money = Money.new(value, #{currency})
100
+ #{write_preferred_currency}
45
101
  elsif value.kind_of?(Money)
102
+ #{write_preferred_currency}
46
103
  #{attr_name}_money = value.convert(#{currency})
47
104
  else
48
105
  throw "Bad money format \#{value.inspect}"
@@ -53,6 +110,7 @@ def #{attr_name}=(value)
53
110
  value
54
111
  end
55
112
  def #{attr_name}_before_type_cast
113
+ # FIX ME, User cannot specify Currency
56
114
  x = #{attr_name}
57
115
  x &&= x.format(:no_symbol, :no_currency, :no_thousands)
58
116
  x
@@ -1,21 +1,24 @@
1
- #
2
- # External representation mixins
3
- #
4
- class Integer # Exact
1
+ # External representation mixin
2
+ class Integer
3
+ # Exact
5
4
  def Money_rep(currency)
6
5
  Integer(self * currency.scale)
7
6
  end
8
7
  end
9
8
 
10
9
 
11
- class Float # Inexact
10
+ # External representation mixin
11
+ class Float
12
+ # Inexact
12
13
  def Money_rep(currency)
13
14
  Integer(self * currency.scale)
14
15
  end
15
16
  end
16
17
 
17
18
 
18
- class String # Exact
19
+ # External representation mixin
20
+ class String
21
+ # Exact
19
22
  def Money_rep(currency)
20
23
  x = currency.parse(self, :currency => currency)
21
24
  x.rep if x.kind_of?(Currency::Money)
@@ -1,49 +1,73 @@
1
+ # -*- ruby -*-
2
+ #
3
+ # = Currency::Currency
4
+ #
5
+ # Represents a currency.
6
+ #
7
+ #
8
+
1
9
  module Currency
2
10
  #include Currency::Exceptions
3
11
 
4
12
  class Currency
5
- # Create a new currency
13
+ # Create a new currency.
14
+ # This should only be called from Currency::CurrencyFactory.
6
15
  def initialize(code, symbol = nil, scale = 100)
7
16
  self.code = code
8
17
  self.symbol = symbol
9
18
  self.scale = scale
10
19
  end
11
20
 
21
+ # Returns the Currency object from the default CurrencyFactory
22
+ # by its 3-letter uppercase Symbol name, such as :USD, or :CAD.
12
23
  def self.get(code)
13
24
  CurrencyFactory.default.get_by_code(code)
14
25
  end
15
26
 
27
+ # Internal method for converting currency codes to internal
28
+ # Symbol format.
16
29
  def self.cast_code(x)
17
30
  x = x.upcase.intern if x.kind_of?(String)
18
- raise InvalidCurrencyCode.new(x) unless x.kind_of?(Symbol)
31
+ raise Exception::InvalidCurrencyCode.new(x) unless x.kind_of?(Symbol)
32
+ raise Exception::InvalidCurrencyCode.new(x) unless x.to_s.length == 3
19
33
  x
20
34
  end
21
35
 
36
+ # Returns the hash of the Currency's code.
22
37
  def hash
23
38
  @code.hash
24
39
  end
25
40
 
41
+ # Returns true if the Currency's are equal.
26
42
  def eql?(x)
27
43
  self.class == x.class && @code == x.code
28
44
  end
29
45
 
46
+ # Returns true if the Currency's are equal.
30
47
  def ==(x)
31
48
  self.class == x.class && @code == x.code
32
49
  end
33
50
 
34
- # Accessors
51
+ # Get the Currency's code.
35
52
  def code
36
53
  @code
37
54
  end
55
+
56
+
57
+ # Client should never call this directly.
38
58
  def code=(x)
39
59
  x = self.class.cast_code(x) unless x.nil?
40
60
  @code = x
41
61
  #$stderr.puts "#{self}.code = #{@code}"; x
42
62
  end
43
63
 
64
+ # Get the Currency's scale factor.
65
+ # E.g: the :USD scale factor is 100.
44
66
  def scale
45
67
  @scale
46
68
  end
69
+
70
+ # Client should never call this directly.
47
71
  def scale=(x)
48
72
  @scale = x
49
73
  return x if x.nil?
@@ -53,21 +77,32 @@ module Currency
53
77
  x
54
78
  end
55
79
 
80
+ # Get the Currency's scale factor.
81
+ # E.g: the :USD scale factor is 2, where 10 ^ 2 == 100.
56
82
  def scale_exp
57
83
  @scale_exp
58
84
  end
59
85
 
86
+ # Get the Currency's symbol.
87
+ # E.g. :USD, :CAD, etc.
60
88
  def symbol
61
89
  @symbol
62
90
  end
91
+
92
+ # Client should never call this directly.
63
93
  def symbol=(x)
64
94
  @symbol = x
65
95
  end
66
96
 
67
- # Parse a Money string
97
+ # Parse a Money string.
98
+ # Options:
99
+ # :currency => Currency object.
100
+ # Look for a matching currency code at the beginning or end of the string.
101
+ # If the currency does not match IncompatibleCurrency is raised.
102
+ #
68
103
  def parse(str, *opt)
69
104
  x = str
70
- opt = Hash.[](*opt)
105
+ opt = Hash[*opt]
71
106
 
72
107
  md = nil # match data
73
108
 
@@ -79,7 +114,7 @@ module Currency
79
114
  x = md[2]
80
115
  if curr != self
81
116
  if opt[:currency] && opt[:currency] != curr
82
- raise IncompatibleCurrency.new("#{str} #{opt[:currency].code}")
117
+ raise Exception::IncompatibleCurrency.new("#{str} #{opt[:currency].code}")
83
118
  end
84
119
  return Money.new(x, curr);
85
120
  end
@@ -89,7 +124,7 @@ module Currency
89
124
  x = md[1]
90
125
  if curr != self
91
126
  if opt[:currency] && opt[:currency] != curr
92
- raise IncompatibleCurrency.new("#{str} #{opt[:currency].code}")
127
+ raise Exception::IncompatibleCurrency.new("#{str} #{opt[:currency].code}")
93
128
  end
94
129
  return Money.new(x, curr);
95
130
  end
@@ -136,7 +171,7 @@ module Currency
136
171
  else
137
172
  # $stderr.puts "'#{self}'.parse(#{str}) => ??? '#{x}'"
138
173
  #x.to_f.Money_rep(self)
139
- raise InvalidMoneyString.new("#{str} #{self}")
174
+ raise Exception::InvalidMoneyString.new("#{str} #{self}")
140
175
  end
141
176
  end
142
177
 
@@ -194,22 +229,22 @@ module Currency
194
229
  # @code.to_s
195
230
  #end
196
231
 
197
- # Convience
198
- # Default currency
232
+ # Returns the default CurrencyFactory's currency.
199
233
  def self.default
200
234
  CurrencyFactory.default.currency
201
235
  end
202
236
 
237
+ # Returns the USD Currency.
203
238
  def self.USD
204
239
  CurrencyFactory.default.USD
205
240
  end
206
241
 
242
+ # Returns the CAD Currency.
207
243
  def self.CAD
208
244
  CurrencyFactory.default.CAD
209
245
  end
210
- end
211
-
212
246
 
213
- # END MODULE
214
- end
247
+ end # class
248
+
249
+ end # module
215
250
 
@@ -2,5 +2,5 @@
2
2
  # This file is auto-generated by build scripts.
3
3
  # See: rake update_version
4
4
  module Currency
5
- CurrencyVersion = '0.1.2'
5
+ CurrencyVersion = '0.2.0'
6
6
  end
@@ -1,15 +1,41 @@
1
1
  module Currency
2
- class Error < Exception; end
3
-
4
- class InvalidMoneyString < Error
2
+ module Exception
5
3
  end
4
+ end
6
5
 
7
- class InvalidCurrencyCode < Error
8
- end
6
+ # Base class for all Currency::Exception.
7
+ class Currency::Exception::Base < Exception
8
+ end
9
+
10
+ module Currency
11
+ module Exception
12
+ # Error during string parsing.
13
+ class InvalidMoneyString < Base
14
+ end
9
15
 
10
- class IncompatibleCurrency < Error
11
- end
16
+ # Error in Currency code formeat.
17
+ class InvalidCurrencyCode < Base
18
+ end
19
+
20
+ # Error during conversion between currencies.
21
+ class IncompatibleCurrency < Base
22
+ end
23
+
24
+ # Error if an Exchange is not defined.
25
+ class UndefinedExchange < Base
26
+ end
27
+
28
+ # Error if a Currency is unknown.
29
+ class UnknownCurrency < Base
30
+ end
31
+
32
+ # Error if an Exchange cannot provide an Exchange::Rate.
33
+ class UnknownRate < Base
34
+ end
35
+
36
+ # Error if an Exchange::Rate is not valid.
37
+ class InvalidRate < Base
38
+ end
12
39
 
13
- class UndefinedExchange < Error
14
40
  end
15
41
  end
@@ -1,22 +1,44 @@
1
+ # -*- ruby -*-
2
+ #
3
+ # = Currency::Exchange
4
+ #
5
+ # The Currency::Exchange package is responsible for
6
+ # the conversion between currencies.
7
+ #
8
+
1
9
  module Currency
2
10
  module Exchange
3
-
4
11
  @@default = nil
12
+ @@current = nil
13
+
14
+ # Returns the default Currency::Exchange object.
15
+ #
16
+ # If one is not specfied an instance of Currency::Exchange::Base is
17
+ # created. Currency::Exchange::Base cannot service any
18
+ # conversion rate requests.
5
19
  def self.default
6
20
  @@default ||= Base.new
7
21
  end
22
+
23
+ # Sets the default Currency::Exchange object.
8
24
  def self.default=(x)
9
25
  @@default = x
10
26
  end
11
27
 
12
- @@current = nil
28
+ # Returns the current Currency::Exchange object used during
29
+ # explicit and implicit Money conversions.
30
+ #
31
+ # If #current= has not been called and #default= has not been called,
32
+ # then UndefinedExchange is raised.
13
33
  def self.current
14
- @@current || self.default || (raise UndefinedExchange, "Currency::Exchange.current not defined")
34
+ @@current || self.default || (raise Exception::UndefinedExchange.new("Currency::Exchange.current not defined"))
15
35
  end
36
+
37
+ # Sets the current Currency::Exchange object used during
38
+ # explicit and implicit Money conversions.
16
39
  def self.current=(x)
17
40
  @@current = x
18
41
  end
19
-
20
42
  end
21
43
  end
22
44
 
@@ -1,3 +1,14 @@
1
+ # -*- ruby -*-
2
+ #
3
+ # = Currency::Exchange::Base
4
+ #
5
+ # The Currency::Exchange::Base class is the base class for
6
+ # currency exchange rate providers.
7
+ #
8
+ # Currency::Exchange::Base subclasses are Currency::Exchange::Rate
9
+ # factories.
10
+ #
11
+
1
12
  module Currency
2
13
  module Exchange
3
14
 
@@ -7,42 +18,54 @@ module Exchange
7
18
 
8
19
  def initialize(*opt)
9
20
  @name = nil
10
- @exchange_rate = { }
21
+ @rate = { }
11
22
  end
12
23
 
24
+ # Converts Money m in Currency c1 to a new
25
+ # Money value in Currency c2.
13
26
  def convert(m, c2, c1 = nil)
14
27
  c1 = m.currency if c1 == nil
15
28
  if ( c1 == c2 )
16
29
  m
17
30
  else
18
- Money.new(exchange_rate(c1, c2).convert(m, c1), c2)
31
+ Money.new(rate(c1, c2).convert(m, c1), c2)
19
32
  end
20
33
  end
21
34
 
22
- def clear_exchange_rates
23
- @exchange_rate.empty!
35
+ # Flush all cached Rate.
36
+ def clear_rates
37
+ @rate.empty!
24
38
  end
25
39
 
26
- def clear_exchange_rate(c1, c2, recip = true)
27
- @exchange_rate[c1.code.to_s + c2.code.to_s] = nil
28
- @exchange_rate[c2.code.to_s + c1.code.to_s] = nil if recip
40
+ # Flush any cached Rate between Currency c1 and c2.
41
+ def clear_rate(c1, c2, recip = true)
42
+ @rate[c1.code.to_s + c2.code.to_s] = nil
43
+ @rate[c2.code.to_s + c1.code.to_s] = nil if recip
29
44
  end
30
45
 
31
- def exchange_rate(c1, c2)
32
- (@exchange_rate[c1.code.to_s + c2.code.to_s] ||= load_exchange_rate(c1, c2)) ||
33
- (@exchange_rate[c2.code.to_s + c1.code.to_s] ||= load_exchange_rate(c2, c1))
46
+ # Returns the Rate between Currency c1 and c2.
47
+ #
48
+ # This will call #get_rate(c1, c2) if the
49
+ # Rate has not already been cached.
50
+ def rate(c1, c2)
51
+ (@rate[c1.code.to_s + c2.code.to_s] ||= get_rate(c1, c2)) ||
52
+ (@rate[c2.code.to_s + c1.code.to_s] ||= get_rate(c2, c1))
34
53
  end
35
54
 
36
55
 
37
- def load_exchange_rate(c1, c2)
38
- raise "Subclass responsibility: load_exchange_rate"
56
+ # Determines and creates the Rate between Currency c1 and c2.
57
+ #
58
+ # Subclasses are required to implement this method.
59
+ def get_rate(c1, c2)
60
+ raise Exception::UnknownRate.new("Subclass responsibility: get_rate")
39
61
  end
40
62
 
63
+ # Returns a simple string rep of an Exchange object.
41
64
  def to_s
42
65
  "#<#{self.class.name} #{self.name && self.name.inspect}>"
43
66
  end
44
67
 
45
- end
68
+ end # class
46
69
 
47
70
 
48
71
  end # module
@@ -6,6 +6,7 @@ module Exchange
6
6
  @c1 = c1
7
7
  @c2 = c2
8
8
  @rate = c1_to_c2_rate
9
+ raise Exception::InvalidRate.new(@rate) unless @rate > 0.0
9
10
  @source = source
10
11
  @date = date || Time.now
11
12
  @reciprocal = recip
@@ -1,10 +1,13 @@
1
+ # This class is a test Exchange.
2
+ # It can convert only between USD and CAD.
3
+
1
4
  module Currency
2
5
  module Exchange
3
6
 
4
- # This class is a text Exchange.
5
- # It can convert only between USD and CAD
6
7
  class Test < Base
7
8
  @@instance = nil
9
+
10
+ # Returns a singleton instance.
8
11
  def self.instance(*opts)
9
12
  @@instance ||= self.new(*opts)
10
13
  end
@@ -13,10 +16,11 @@ module Exchange
13
16
  super(*opts)
14
17
  end
15
18
 
16
- # Sample constant.
19
+ # Test rate from :USD to :CAD.
17
20
  def self.USD_CAD; 1.1708; end
18
21
 
19
- def load_exchange_rate(c1, c2)
22
+ # Returns test Rate for USD and CAD pairs.
23
+ def get_rate(c1, c2)
20
24
  # $stderr.puts "load_exchange_rate(#{c1}, #{c2})"
21
25
  rate = 0.0
22
26
  if ( c1.code == :USD && c2.code == :CAD )
@@ -24,11 +28,12 @@ module Exchange
24
28
  end
25
29
  rate > 0 ? Rate.new(c1, c2, rate, self) : nil
26
30
  end
27
- end
31
+
32
+ end # class
28
33
 
29
34
  end # module
30
35
  end # module
31
36
 
32
- # Install as current
37
+ # Install as default.
33
38
  Currency::Exchange.default = Currency::Exchange::Test.instance
34
39
 
@@ -1,17 +1,24 @@
1
+ # Connects to http://xe.com and parses "XE.com Quick Cross Rates"
2
+ # from home page HTML.
3
+
1
4
  require 'net/http'
2
5
  require 'open-uri'
3
6
 
4
7
  module Currency
5
8
  module Exchange
6
- # Represents connects to http://xe.com and groks "XE.com Quick Cross Rates"
7
9
 
8
10
  class Xe < Base
9
11
  @@instance = nil
12
+
13
+ # Returns a singleton instance.
10
14
  def self.instance(*opts)
11
15
  @@instance ||= self.new(*opts)
12
16
  end
13
17
 
18
+ # Defaults to "http://xe.com/"
14
19
  attr_accessor :uri
20
+
21
+ # This Exchange's name is the same as its #uri.
15
22
  def name
16
23
  uri
17
24
  end
@@ -22,6 +29,10 @@ module Exchange
22
29
  @rates = nil
23
30
  end
24
31
 
32
+ # Returns a cached Hash of rates:
33
+ #
34
+ # xe.rates[:USD][:CAD] => 1.01
35
+ #
25
36
  def rates
26
37
  return @rates if @rates
27
38
 
@@ -34,6 +45,7 @@ module Exchange
34
45
  @rates
35
46
  end
36
47
 
48
+ # Returns the URI content.
37
49
  def get_page
38
50
  data = open(uri) { |data| data.read }
39
51
 
@@ -42,6 +54,8 @@ module Exchange
42
54
  data
43
55
  end
44
56
 
57
+ # Parses http://xe.com homepage HTML for
58
+ # quick rates of 10 currencies.
45
59
  def parse_page_rates(data = nil)
46
60
  data = get_page unless data
47
61
 
@@ -114,7 +128,9 @@ module Exchange
114
128
  rate
115
129
  end
116
130
 
117
- def load_exchange_rate(c1, c2)
131
+ # Loads cached rates from xe.com and creates Rate objects
132
+ # for 10 currencies.
133
+ def get_rate(c1, c2)
118
134
  rates # Load rates
119
135
 
120
136
  # $stderr.puts "load_exchange_rate(#{c1}, #{c2})"
@@ -134,12 +150,13 @@ module Exchange
134
150
 
135
151
  rate > 0 ? Rate.new(c1, c2, rate, self, @rate_timestamp) : nil
136
152
  end
137
- end
153
+
154
+ end # class
138
155
 
139
156
  end # module
140
157
  end # module
141
158
 
142
159
 
143
- # Install as current
160
+ # Install as default.
144
161
  Currency::Exchange.default = Currency::Exchange::Xe.instance
145
162
 
@@ -1,17 +1,54 @@
1
+ # -*- ruby -*-
2
+ #
3
+ # = Currency::Money
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
+ # TODO:
11
+ # * Need to store a time, so we can use historical FX rates.
12
+ #
13
+
1
14
  module Currency
2
15
 
3
- # Represents an amount of money in a particular currency.
16
+ # Use this function instead of Money#new:
17
+ #
18
+ # Currency::Money("12.34", :CAD)
4
19
  #
5
- # NOTE: do we need to store a time, so we can use
6
- # historical FX rates to convert?
20
+ # not
7
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
+
8
29
  class Money
9
30
  include Comparable
10
31
 
11
-
12
- # Construct from a pre-scaled external representation:
13
- # Float, Integer, String, etc.
32
+ #
33
+ # Construct a Money value object
34
+ # from a pre-scaled external representation:
35
+ # where x is a Float, Integer, String, etc.
36
+ #
37
+ # If a currency is not specified, Currency#default is used.
38
+ #
39
+ # x.Money_rep(currency)
40
+ #
41
+ # is invoked to coerce x into a Money representation value.
42
+ #
43
+ # For example:
44
+ #
45
+ # 123.Money_rep(:USD) => 12300
46
+ #
47
+ # Because the USD Currency object has a #scale of 100
48
+ #
14
49
  # See #Money_rep(currency) mixin.
50
+ # See Currency#Money() function.
51
+ #
15
52
  def initialize(x, currency = nil)
16
53
  # Xform currency
17
54
  currency = Currency.default if currency.nil?
@@ -32,6 +69,7 @@ module Currency
32
69
  def self.us_dollar(x)
33
70
  self.new(x, :USD)
34
71
  end
72
+ # Compatibility with Money package.
35
73
  def cents
36
74
  @rep
37
75
  end
@@ -43,33 +81,42 @@ module Currency
43
81
  x.set_rep(r)
44
82
  x
45
83
  end
84
+ # Construct from post-scaled internal representation
85
+ # using the same currency.
86
+ # x = Currency::Money.new("1.98", :USD)
87
+ # x.new_rep(100) => "$1.00 USD"
88
+ #
46
89
  def new_rep(r)
47
90
  x = self.class.new(0, @currency)
48
91
  x.set_rep(r)
49
92
  x
50
93
  end
51
94
 
52
- # CLIENTS SHOULD NEVER CALL set_rep DIRECTLY!
95
+ # Do not call this method directly
96
+ # CLIENTS SHOULD NEVER CALL set_rep DIRECTLY.
97
+ # You have been warned in ALL CAPS.
53
98
  def set_rep(r)
54
99
  r = r.to_i unless r.kind_of?(Integer)
55
100
  @rep = r
56
101
  end
57
102
 
103
+ # Returns the money representation for the requested currency.
58
104
  def Money_rep(currency)
59
105
  $stderr.puts "@currency != currency (#{@currency.inspect} != #{currency.inspect}" unless @currency == currency
60
106
  @rep
61
107
  end
62
108
 
109
+ # Returns the money representation (usually an Integer).
63
110
  def rep
64
111
  @rep
65
112
  end
66
113
 
67
- # Get the money's Currency
114
+ # Get the money's Currency.
68
115
  def currency
69
116
  @currency
70
117
  end
71
118
 
72
- # Convert Money to another Currency
119
+ # Convert Money to another Currency.
73
120
  def convert(currency)
74
121
  currency = Currency.default if currency.nil?
75
122
  currency = Currency.get(currency) unless currency.kind_of?(Currency)
@@ -80,17 +127,24 @@ module Currency
80
127
  end
81
128
  end
82
129
 
83
- # Relational operations on Money values.
130
+ # Hash for hash table: both value and currency.
131
+ # See #eql? below.
84
132
  def hash
85
133
  @rep.hash ^ @currency.hash
86
134
  end
87
135
 
136
+ # True if money values have the same value and currency.
88
137
  def eql?(x)
89
- @rep == x.rep && @currency == x.currency
138
+ self.class == x.class &&
139
+ @rep == x.rep &&
140
+ @currency == x.currency
90
141
  end
91
142
 
143
+ # True if money values have the same value and currency.
92
144
  def ==(x)
93
- @rep == x.rep && @currency == x.currency
145
+ self.class == x.class &&
146
+ @rep == x.rep &&
147
+ @currency == x.currency
94
148
  end
95
149
 
96
150
  def <=>(x)
@@ -104,70 +158,85 @@ module Currency
104
158
  # Operations on Money values.
105
159
 
106
160
 
161
+ # - Money => Money
162
+ # Negates a Money value.
107
163
  def -@
108
- # - Money => Money
109
164
  new_rep(- @rep)
110
165
  end
111
166
 
167
+ # Money + (Number|Money) => Money
168
+ #
112
169
  # Right side maybe coerced to Money.
113
170
  def +(x)
114
- # Money + (Number|Money) => Money
115
171
  new_rep(@rep + x.Money_rep(@currency))
116
172
  end
117
173
 
174
+ # Money - (Number|Money) => Money
175
+ #
118
176
  # Right side maybe coerced to Money.
119
177
  def -(x)
120
- # Money - (Number|Money) => Money
121
178
  new_rep(@rep - x.Money_rep(@currency))
122
179
  end
123
180
 
124
- # Right side must be number
181
+ # Money * Number => Money
182
+ #
183
+ # Right side must be number.
125
184
  def *(x)
126
- # Money * Number => Money
127
- new_rep(@rep * x)
185
+ new_rep(@rep * x)
128
186
  end
129
187
 
130
- # Right side must be number or Money
188
+ # Money / Money => Number (ratio)
189
+ # Money / Number => Money
190
+ #
191
+ # Right side must be a number or Money.
192
+ # Right side Integers are not coerced to Float before
193
+ # division.
131
194
  def /(x)
132
195
  if x.kind_of?(self.class)
133
- # Money / Money => ratio
134
196
  (@rep.to_f) / (x.Money_rep(@currency).to_f)
135
197
  else
136
- # Money / Number => Money
137
198
  new_rep(@rep / x)
138
199
  end
139
200
  end
140
201
 
202
+ # Formats the Money value as a String.
141
203
  def format(*opt)
142
204
  @currency.format(self, *opt)
143
205
  end
144
206
 
145
- # Coercions
207
+ # Formats the Money value as a String.
146
208
  def to_s(*opt)
147
209
  @currency.format(self, *opt)
148
210
  end
149
211
 
212
+ # Coerces the Money's value to a Float.
213
+ # May cause loss of precision.
150
214
  def to_f
151
215
  Float(@rep) / @currency.scale
152
216
  end
153
217
 
218
+ # Coerces the Money's value to an Integer.
219
+ # May cause loss of precision.
154
220
  def to_i
155
221
  @rep / @currency.scale
156
222
  end
157
223
 
224
+ # True if the Money's value is zero.
158
225
  def zero?
159
226
  @rep == 0
160
227
  end
161
228
 
229
+ # True if the Money's value is greater than zero.
162
230
  def positive?
163
231
  @rep > 0
164
232
  end
165
233
 
234
+ # True if the Money's value is less than zero.
166
235
  def negative?
167
236
  @rep < 0
168
237
  end
169
238
 
170
- # Implicit currency conversions?
239
+ # Returns the Money's value representation in another currency.
171
240
  def Money_rep(currency)
172
241
  # Attempt conversion?
173
242
  if @currency != currency
@@ -178,6 +247,8 @@ module Currency
178
247
  end
179
248
  end
180
249
 
250
+ # Basic inspection, with symbol and currency code.
251
+ # The standard #inspect method is available as #inspect_deep.
181
252
  def inspect(*opts)
182
253
  self.format(:with_symbol, :with_currency).inspect
183
254
  end
@@ -185,9 +256,9 @@ module Currency
185
256
  # How to alias a method defined in an object superclass in a different class:
186
257
  define_method(:inspect_deep, Object.instance_method(:inspect))
187
258
  # How call a method defined in a superclass from a method with a different name:
188
- #def inspect_deep(*opts)
189
- # self.class.superclass.instance_method(:inspect).bind(self).call
190
- #end
259
+ # def inspect_deep(*opts)
260
+ # self.class.superclass.instance_method(:inspect).bind(self).call
261
+ # end
191
262
 
192
263
  end # class
193
264
 
data/test/money_test.rb CHANGED
@@ -1,8 +1,8 @@
1
- # REAL
1
+
2
2
  #require File.dirname(__FILE__) + '/../test_helper'
3
3
 
4
4
  require 'test/test_base'
5
- require 'currency' # For :type => :money
5
+ require 'currency'
6
6
 
7
7
  module Currency
8
8
 
@@ -105,7 +105,7 @@ class MoneyTest < TestBase
105
105
  end
106
106
 
107
107
  def test_op
108
- # Using default load_exchange_rate
108
+ # Using default get_rate
109
109
  assert_not_nil usd = Money.new(123.45, :USD)
110
110
  assert_not_nil cad = Money.new(123.45, :CAD)
111
111
 
@@ -160,6 +160,14 @@ class MoneyTest < TestBase
160
160
  assert_equal_float Exchange::Test.USD_CAD, m, 0.0001
161
161
  end
162
162
 
163
+ def test_invalid_currency_code
164
+ assert_raise Exception::InvalidCurrencyCode do
165
+ Money.new(123, :asdf)
166
+ end
167
+ assert_raise Exception::InvalidCurrencyCode do
168
+ Money.new(123, 5)
169
+ end
170
+ end
163
171
  end
164
172
 
165
173
  end # module
data/test/xe_test.rb CHANGED
@@ -1,6 +1,3 @@
1
- # REAL
2
- #require File.dirname(__FILE__) + '/../test_helper'
3
-
4
1
  require 'test/test_base'
5
2
  require 'currency' # For :type => :money
6
3
  require 'currency/exchange/xe'
metadata CHANGED
@@ -1,10 +1,10 @@
1
1
  --- !ruby/object:Gem::Specification
2
- rubygems_version: 0.8.11
2
+ rubygems_version: 0.9.0
3
3
  specification_version: 1
4
4
  name: currency
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.1.2
7
- date: 2006-10-30 00:00:00 -05:00
6
+ version: 0.2.0
7
+ date: 2006-10-31 00:00:00 -05:00
8
8
  summary: Currency models currencies, monetary values, foreign exchanges.
9
9
  require_paths:
10
10
  - lib
@@ -26,6 +26,7 @@ required_ruby_version: !ruby/object:Gem::Version::Requirement
26
26
  platform: ruby
27
27
  signing_key:
28
28
  cert_chain:
29
+ post_install_message:
29
30
  authors:
30
31
  - Kurt Stephens
31
32
  files: