root/trunk/lib/globalize/models/currency.rb

Revision 21, 5.4 kB (checked in by olivier, 2 years ago)

Load ./for-1.1/ into trunk.

Line 
1 module Globalize
2
3 =begin rdoc
4   This is what you use for representing money in your ActiveRecord models.
5   It stores values as integers internally and in the database, to safeguard
6   precision and rounding.
7
8   More importantly for globalization freaks, it prints out the amount correctly
9   in the current locale, via the handy #format method. Try it!
10
11   Example:
12
13     class Product < ActiveRecord::Base
14       composed_of :price, :class_name => "Globalize::Currency",
15         :mapping => [ %w(price cents) ]
16     end
17
18     product.price -> "SFr. 483'232.43"
19 =end
20   class Currency
21     include Comparable
22
23     attr_reader :cents
24    
25     class CurrencyError < StandardError# :nodoc:
26     end
27
28     # Creates a new Currency object, given cents. This is used to tie into
29     # the ActiveRecord aggregation stuff.
30     def initialize(cents)
31       @cents = cents.nil? ? nil : cents.to_i
32     end
33
34     @@no_cents = false
35     def self.no_cents; @@no_cents end
36     def self.no_cents=(val); @@no_cents = val end
37
38     @@free = Currency.new(0)
39     @@free.freeze
40
41     # Returns the Currency object representing a price of 0.
42     def self.free
43       @@free
44     end
45
46     @@na = Currency.new(nil)
47     @@na.freeze
48
49     # Returns the Currency object representing an unknown price.
50     def self.na
51       @@na
52     end
53
54     def <=>(other)
55       if other.respond_to? :cents
56         if na?
57           other.na? ? 0 : 1
58         else
59           other.na? ? -1 : cents <=> other.cents
60         end
61       elsif other.kind_of? Integer
62         na? ? 1 : cents <=> other
63       else
64         raise "can only compare with money or integer"
65       end     
66     end
67
68     def +(other_money)
69       raise TypeError, "parameter to Currency#+ must be Currency object" unless
70         other_money.kind_of? Currency
71       (na? || other_money.na?) ? Currency.na :
72         Currency.new(cents + other_money.cents)
73     end
74
75     def -(other_money)
76       raise TypeError, "parameter to Currency#+ must be Currency object" unless
77         other_money.kind_of? Currency
78       (na? || other_money.na?) ? Currency.na :
79         Currency.new(cents - other_money.cents)
80     end
81
82     # Multiply money by amount
83     def *(amount)
84       return Currency.new(nil) if na?
85       new_cents = amount * cents;
86       new_cents = new_cents.round if new_cents.respond_to? :round
87       Currency.new(new_cents)
88     end
89
90     # Divide money by amount
91     def /(amount)
92       return Currency.new(nil) if na?
93       new_cents = cents / amount;
94       new_cents = new_cents.round if new_cents.respond_to? :round
95       Currency.new(new_cents)
96     end
97
98     # Returns the formatted version of the amount in local currency.
99     # If <tt>:code => true</tt> is specified, format using international
100     # 3-letter currency code. If <tt>:country</tt> is specified as well,
101     # use that country's currency code for formatting.
102     def format(options = {})
103       return :no_price_available.t("call for price") if na?
104
105       force_cents = options[:force_cents]
106                         if options[:code]
107         currency_code = options[:country] ?
108           options[:country].currency_code :
109           ( Locale.active? ? Locale.active.currency_code : nil )
110         currency_code ?
111                                         self.amount(false, force_cents) + " " + currency_code :
112                                         self.amount(false, force_cents)
113       else
114         if Locale.active?
115           fmt = Locale.active.currency_format || '$%n'
116           fmt.sub('%n', self.amount(false, force_cents))
117         else
118           self.amount false, force_cents
119         end
120       end
121     end
122
123     # Returns the formatted version of the amount, but without the currency symbol.
124     # If +unlocalized+ is true, do not format the number according to the current locale,
125     # so <tt>Currency.new(1234567) -> 12345.67</tt>. This is useful for sending the
126     # amount to payment gateways.
127     def amount(unlocalized = false, force_cents = false)
128       return nil if na?
129       decimal_sep = unlocalized ?
130         '.' :
131         (Locale.active? ?
132           (Locale.active.currency_decimal_sep || Locale.active.decimal_sep || '.') :
133           '.')
134       dollar_str = unlocalized ? dollar_part.to_s : dollar_part.localize
135       result = dollar_str
136       result << decimal_sep + sprintf("%02d", cent_part) unless
137         self.class.no_cents && !force_cents
138       return result
139     end
140
141     # Same as #format with no arguments.
142     def to_s
143       self.format
144     end
145
146     # Parse a string or number into a currency object. Easier to use than #new.
147     def self.parse(num)
148       case
149       when num.is_a?(String)
150         raise ArgumentError, "Not an amount (#{num})" if num.delete("^0-9").empty?
151         _dollars, _cents = num.delete("^0-9.").split('.', 2)
152         _cents = _cents ? _cents[0,2] : 0
153         Currency.new(_dollars.to_i * 100 + _cents.to_i)
154       when num.is_a?(Numeric)
155         Currency.new(num * 100)
156       when num.is_a?(NilClass)
157         Currency.na
158       else
159         raise ArgumentError, "Unrecognized object #{num.class.name} for Currency"
160       end
161     end
162
163     # Conversion to self
164     def to_currency
165       self
166     end
167
168     # Is the value 0? Is it free?
169     def empty?
170       cents == 0
171     end
172
173     # Is the value unknown?
174     def na?
175       cents.nil?
176     end
177
178     private
179       def dollar_part
180         na? ? nil : cents / 100
181       end
182
183       def cent_part
184         na? ? nil : cents % 100
185       end
186
187   end
188 end
Note: See TracBrowser for help on using the browser.