currency 0.1.2 → 0.2.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/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: