money 3.1.0 → 3.1.5

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md CHANGED
@@ -1,3 +1,30 @@
1
+ Money 3.1.5
2
+ ===========
3
+
4
+ Features
5
+ --------
6
+ - Added support for creating objects with the main monetary unit instead of
7
+ cents.
8
+ ([#issue/25](http://github.com/RubyMoney/money/issues/25))
9
+ - Deprecated `Money#format` with separate params instead of Hash. Deprecation
10
+ target set to Money 3.5.0.
11
+ ([#issue/31](http://github.com/RubyMoney/money/issues/31))
12
+ - Deprecated `Money#new(0, :currency => "EUR")` in favor of
13
+ `Money#new(0, "EUR")`. Deprecation target set to Money 3.5.0.
14
+ ([#issue/31](http://github.com/RubyMoney/money/issues/31))
15
+ - Throw ArgumentError when trying to multiply two Money objects together.
16
+ ([#issue/29](http://github.com/RubyMoney/money/issues/29))
17
+ - Update Money#parse to use :subunit_to_unit
18
+ ([#issue/30](http://github.com/RubyMoney/money/issues/30))
19
+
20
+ Bugfixes
21
+ --------
22
+ - Downgraded required_rubygems_version to >= 1.3.6.
23
+ ([#issue/26](http://github.com/RubyMoney/money/issues/26))
24
+ - Use BigDecimal when floating point calculations are needed.
25
+ - Ruby 1.9.2 compatibility enhancements.
26
+
27
+
1
28
  Money 3.1.0
2
29
  ===========
3
30
 
data/lib/money.rb CHANGED
@@ -20,7 +20,8 @@
20
20
  # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
21
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
22
 
23
- $LOAD_PATH << File.expand_path(File.dirname(__FILE__))
23
+ require 'bigdecimal'
24
+ require 'money/currency'
24
25
  require 'money/money'
25
26
  require 'money/defaults'
26
27
  require 'money/core_extensions'
@@ -81,9 +81,10 @@ class Money
81
81
  end
82
82
  _to_currency_ = Currency.wrap(to_currency)
83
83
 
84
- cents = from.cents / (from.currency.subunit_to_unit.to_f / _to_currency_.subunit_to_unit.to_f)
84
+ cents = from.cents / (BigDecimal.new(from.currency.subunit_to_unit.to_s) / BigDecimal.new(_to_currency_.subunit_to_unit.to_s))
85
85
 
86
- ex = cents * rate
86
+ ex = cents * BigDecimal.new(rate.to_s)
87
+ ex = ex.to_f
87
88
  ex = if block_given?
88
89
  block.call(ex)
89
90
  elsif @rounding_method
@@ -151,8 +152,7 @@ class Money
151
152
  # Available formats are +:json+, +:ruby+ and +:yaml+.
152
153
  #
153
154
  # @param [Symbol] format Request format for the resulting string.
154
- # @param [optional, String] file Optional file location to write the
155
- # rates to.
155
+ # @param [String] file Optional file location to write the rates to.
156
156
  #
157
157
  # @return [String]
158
158
  #
@@ -1,37 +1,34 @@
1
1
  # Open +Numeric+ to add new method.
2
2
  class Numeric
3
- # Converts this numeric to a +Money+ object in the default currency.
3
+
4
+ # Converts this numeric into a +Money+ object in the given +currency+.
4
5
  #
5
- # @param [optional, Money::Currency, String, Symbol] currency The currency to
6
- # set the resulting +Money+ object to.
6
+ # @param [Currency, String, Symbol] currency
7
+ # The currency to set the resulting +Money+ object to.
7
8
  #
8
9
  # @return [Money]
9
10
  #
10
11
  # @example
11
12
  # 100.to_money #=> #<Money @cents=10000>
12
13
  # 100.37.to_money #=> #<Money @cents=10037>
13
- # require 'bigdecimal'
14
14
  # BigDecimal.new('100').to_money #=> #<Money @cents=10000>
15
- def to_money(currency = Money.default_currency)
16
- currency = Money::Currency.new(currency) unless currency.is_a?(Money::Currency)
17
- amt = self * currency.subunit_to_unit
18
- amt = case amt.class.to_s
19
- when 'BigDecimal'
20
- amt.to_s('F')
21
- else
22
- amt.to_s
23
- end
24
- Money.new(amt.to_i, currency)
15
+ #
16
+ # @see Money.from_numeric
17
+ #
18
+ def to_money(currency = nil)
19
+ Money.from_numeric(self, currency || Money.default_currency)
25
20
  end
21
+
26
22
  end
27
23
 
28
24
  # Open +String+ to add new methods.
29
25
  class String
30
- # Parses the current string and converts it to a +Money+ object. Excess
31
- # characters will be discarded.
26
+
27
+ # Parses the current string and converts it to a +Money+ object.
28
+ # Excess characters will be discarded.
32
29
  #
33
- # @param [optional, Money::Currency, String, Symbol] currency The currency to
34
- # set the resulting +Money+ object to.
30
+ # @param [Currency, String, Symbol] currency
31
+ # The currency to set the resulting +Money+ object to.
35
32
  #
36
33
  # @return [Money]
37
34
  #
@@ -42,147 +39,25 @@ class String
42
39
  # 'USD 100'.to_money #=> #<Money @cents=10000, @currency=#<Money::Currency id: usd>>
43
40
  # '$100 USD'.to_money #=> #<Money @cents=10000, @currency=#<Money::Currency id: usd>>
44
41
  # 'hello 2000 world'.to_money #=> #<Money @cents=200000 @currency=#<Money::Currency id: usd>>
42
+ #
43
+ # @see Money.from_string
44
+ #
45
45
  def to_money(currency = nil)
46
- # Get the currency.
47
- matches = scan /([A-Z]{2,3})/
48
- _currency_ = matches[0] ? matches[0][0] : nil
49
-
50
- # check that currency passed and embedded currency are the same, or only
51
- # one or the other is present.
52
- if currency.nil? and _currency_.nil?
53
- currency = Money.default_currency
54
- elsif currency.nil?
55
- currency = _currency_
56
- elsif _currency_.nil?
57
- currency = currency
58
- elsif currency != _currency_
59
- raise "mismatching currencies"
60
- end
61
-
62
- cents = calculate_cents(self)
63
- Money.new(cents, currency)
46
+ Money.parse(self, currency)
64
47
  end
65
48
 
66
- # Parses the current string and converts it to a +Currency+ object.
49
+ # Converts the current string into a +Currency+ object.
67
50
  #
68
51
  # @return [Money::Currency]
69
52
  #
53
+ # @raise [Money::Currency::UnknownCurrency]
54
+ # If this String reference an unknown currency.
55
+ #
70
56
  # @example
71
57
  # "USD".to_currency #=> #<Money::Currency id: usd>
58
+ #
72
59
  def to_currency
73
60
  Money::Currency.new(self)
74
61
  end
75
62
 
76
- private
77
-
78
- # Takes a number string and attempts to massage out the number.
79
- #
80
- # @param [String] number The string containing a potential number.
81
- #
82
- # @return [Integer]
83
- def calculate_cents(number)
84
- # remove anything that's not a number, potential delimiter, or minus sign
85
- num = number.gsub(/[^\d|\.|,|\'|\s|\-]/, '').strip
86
-
87
- # set a boolean flag for if the number is negative or not
88
- negative = num.split(//).first == "-"
89
-
90
- # if negative, remove the minus sign from the number
91
- num = num.gsub(/^-/, '') if negative
92
-
93
- # gather all separators within the result number
94
- used_separators = num.scan /[^\d]/
95
-
96
- # determine the number of unique separators within the number
97
- #
98
- # e.g.
99
- # $1,234,567.89 would return 2 (, and .)
100
- # $125,00 would return 1
101
- # $199 would return 0
102
- # $1 234,567.89 would raise an error (separators are space, comma, and period)
103
- case used_separators.uniq.length
104
- # no separator or delimiter; major (dollars) is the number, and minor (cents) is 0
105
- when 0 then major, minor = num, 0
106
-
107
- # two separators, so we know the last item in this array is the
108
- # major/minor delimiter and the rest are separators
109
- when 2
110
- separator, delimiter = used_separators.uniq
111
- # remove all separators, split on the delimiter
112
- major, minor = num.gsub(separator, '').split(delimiter)
113
- min = 0 unless min
114
- when 1
115
- # we can't determine if the comma or period is supposed to be a separator or a delimiter
116
- # e.g.
117
- # 1,00 - comma is a delimiter
118
- # 1.000 - period is a delimiter
119
- # 1,000 - comma is a separator
120
- # 1,000,000 - comma is a separator
121
- # 10000,00 - comma is a delimiter
122
- # 1000,000 - comma is a delimiter
123
-
124
- # assign first separator for reusability
125
- separator = used_separators.first
126
-
127
- # separator is used as a separator when there are multiple instances, always
128
- if num.scan(separator).length > 1 # multiple matches; treat as separator
129
- major, minor = num.gsub(separator, ''), 0
130
- else
131
- # ex: 1,000 - 1.0000 - 10001.000
132
- # split number into possible major (dollars) and minor (cents) values
133
- possible_major, possible_minor = num.split(separator)
134
- possible_major ||= "0"
135
- possible_minor ||= "00"
136
-
137
- # if the minor (cents) length isn't 3, assign major/minor from the possibles
138
- # e.g.
139
- # 1,00 => 1.00
140
- # 1.0000 => 1.00
141
- # 1.2 => 1.20
142
- if possible_minor.length != 3 # delimiter
143
- major, minor = possible_major, possible_minor
144
- else
145
- # minor length is three
146
- # let's try to figure out intent of the delimiter
147
-
148
- # the major length is greater than three, which means
149
- # the comma or period is used as a delimiter
150
- # e.g.
151
- # 1000,000
152
- # 100000,000
153
- if possible_major.length > 3
154
- major, minor = possible_major, possible_minor
155
- else
156
- # number is in format ###{sep}### or ##{sep}### or #{sep}###
157
- # handle as , is sep, . is delimiter
158
- if separator == '.'
159
- major, minor = possible_major, possible_minor
160
- else
161
- major, minor = "#{possible_major}#{possible_minor}", 0
162
- end
163
- end
164
- end
165
- end
166
- else
167
- raise ArgumentError, "Invalid currency amount"
168
- end
169
-
170
- # build the string based on major/minor since separator/delimiters have been removed
171
- # avoiding floating point arithmetic here to ensure accuracy
172
- cents = (major.to_i * 100)
173
- # add the minor number as well. this may have any number of digits,
174
- # so we treat minor as a string and truncate or right-fill it with zeroes
175
- # until it becomes a two-digit number string, which we add to cents.
176
- minor = minor.to_s
177
- truncated_minor = minor[0..1]
178
- truncated_minor << "0" * (2 - truncated_minor.size) if truncated_minor.size < 2
179
- cents += truncated_minor.to_i
180
- # respect rounding rules
181
- if minor.size >= 3 && minor[2..2].to_i >= 5
182
- cents += 1
183
- end
184
-
185
- # if negative, multiply by -1; otherwise, return positive cents
186
- negative ? cents * -1 : cents
187
- end
188
63
  end
data/lib/money/money.rb CHANGED
@@ -1,12 +1,11 @@
1
1
  # encoding: utf-8
2
- require 'money/currency'
3
2
  require 'money/bank/variable_exchange'
4
3
 
5
4
  # Represents an amount of money in a given currency.
6
5
  class Money
7
6
  include Comparable
8
7
 
9
- # The value of the money is cents.
8
+ # The value of the money in cents.
10
9
  #
11
10
  # @return [Integer]
12
11
  attr_reader :cents
@@ -101,10 +100,258 @@ class Money
101
100
  Money.new(cents, "EUR")
102
101
  end
103
102
 
103
+
104
+ # Creates a new Money object of +amount+ value in dollars,
105
+ # with given +currency+.
106
+ #
107
+ # The amount value is expressed in +dollars+
108
+ # where the +dollar+ is the main monetary unit,
109
+ # opposite to the subunit-based representation
110
+ # used internally by this library called +cents+.
111
+ #
112
+ # @param [Numeric] amount The money amount, in dollars.
113
+ # @param [Currency, String, Symbol] currency The currency format.
114
+ # @param [Money::Bank::*] bank The exchange bank to use.
115
+ #
116
+ # @return [Money]
117
+ #
118
+ # @example
119
+ # Money.new_with_dollars(100)
120
+ # #=> #<Money @cents=10000 @currency="USD">
121
+ # Money.new_with_dollars(100, "USD")
122
+ # #=> #<Money @cents=10000 @currency="USD">
123
+ # Money.new_with_dollars(100, "EUR")
124
+ # #=> #<Money @cents=10000 @currency="EUR">
125
+ #
126
+ # @see Money.new
127
+ #
128
+ def self.new_with_dollars(amount, currency = Money.default_currency, bank = Money.default_bank)
129
+ money = from_numeric(amount, currency)
130
+ # Hack! You can't change a bank
131
+ money.instance_variable_set("@bank", bank)
132
+ money
133
+ end
134
+
135
+
136
+ # Parses the current string and converts it to a +Money+ object.
137
+ # Excess characters will be discarded.
138
+ #
139
+ # @param [String, #to_s] input The input to parse.
140
+ # @param [Currency, String, Symbol] currency The currency format.
141
+ # The currency to set the resulting +Money+ object to.
142
+ #
143
+ # @return [Money]
144
+ #
145
+ # @raise [ArgumentError] If any +currency+ is supplied and
146
+ # given value doesn't match the one extracted from
147
+ # the +input+ string.
148
+ #
149
+ # @example
150
+ # '100'.to_money #=> #<Money @cents=10000>
151
+ # '100.37'.to_money #=> #<Money @cents=10037>
152
+ # '100 USD'.to_money #=> #<Money @cents=10000, @currency=#<Money::Currency id: usd>>
153
+ # 'USD 100'.to_money #=> #<Money @cents=10000, @currency=#<Money::Currency id: usd>>
154
+ # '$100 USD'.to_money #=> #<Money @cents=10000, @currency=#<Money::Currency id: usd>>
155
+ # 'hello 2000 world'.to_money #=> #<Money @cents=200000 @currency=#<Money::Currency id: usd>>
156
+ #
157
+ # @example Mismatching currencies
158
+ # 'USD 2000'.to_money("EUR") #=> ArgumentError
159
+ #
160
+ # @see Money.from_string
161
+ #
162
+ def self.parse(input, currency = nil)
163
+ i = input.to_s
164
+
165
+ # Get the currency.
166
+ m = i.scan /([A-Z]{2,3})/
167
+ c = m[0] ? m[0][0] : nil
168
+
169
+ # check that currency passed and embedded currency are the same,
170
+ # and negotiate the final currency
171
+ if currency.nil? and c.nil?
172
+ currency = Money.default_currency
173
+ elsif currency.nil?
174
+ currency = c
175
+ elsif c.nil?
176
+ currency = currency
177
+ elsif currency != c
178
+ # TODO: ParseError
179
+ raise ArgumentError, "Mismatching Currencies"
180
+ end
181
+ currency = Money::Currency.wrap(currency)
182
+
183
+ cents = extract_cents(i, currency)
184
+ Money.new(cents, currency)
185
+ end
186
+
187
+ # Converts a String into a Money object treating the +value+
188
+ # as dollars and converting them to the corresponding cents value,
189
+ # according to +currency+ subunit property,
190
+ # before instantiating the Money object.
191
+ #
192
+ # Behind the scenes, this method relies on {Money.from_bigdecimal}
193
+ # to avoid problems with string-to-numeric conversion.
194
+ #
195
+ # @param [String, #to_s] value The money amount, in dollars.
196
+ # @param [Currency, String, Symbol] currency
197
+ # The currency to set the resulting +Money+ object to.
198
+ #
199
+ # @return [Money]
200
+ #
201
+ # @example
202
+ # Money.from_string("100")
203
+ # #=> #<Money @cents=10000 @currency="USD">
204
+ # Money.from_string("100", "USD")
205
+ # #=> #<Money @cents=10000 @currency="USD">
206
+ # Money.from_string("100", "EUR")
207
+ # #=> #<Money @cents=10000 @currency="EUR">
208
+ # Money.from_string("100", "BHD")
209
+ # #=> #<Money @cents=100 @currency="BHD">
210
+ #
211
+ # @see String#to_money
212
+ # @see Money.parse
213
+ #
214
+ def self.from_string(value, currency = Money.default_currency)
215
+ from_bigdecimal(BigDecimal.new(value.to_s), currency)
216
+ end
217
+
218
+ # Converts a Fixnum into a Money object treating the +value+
219
+ # as dollars and converting them to the corresponding cents value,
220
+ # according to +currency+ subunit property,
221
+ # before instantiating the Money object.
222
+ #
223
+ # @param [Fixnum] value The money amount, in dollars.
224
+ # @param [Currency, String, Symbol] currency The currency format.
225
+ #
226
+ # @return [Money]
227
+ #
228
+ # @example
229
+ # Money.from_fixnum(100)
230
+ # #=> #<Money @cents=10000 @currency="USD">
231
+ # Money.from_fixnum(100, "USD")
232
+ # #=> #<Money @cents=10000 @currency="USD">
233
+ # Money.from_fixnum(100, "EUR")
234
+ # #=> #<Money @cents=10000 @currency="EUR">
235
+ # Money.from_fixnum(100, "BHD")
236
+ # #=> #<Money @cents=100 @currency="BHD">
237
+ #
238
+ # @see Fixnum#to_money
239
+ # @see Money.from_numeric
240
+ #
241
+ def self.from_fixnum(value, currency = Money.default_currency)
242
+ currency = Money::Currency.wrap(currency)
243
+ amount = value * currency.subunit_to_unit
244
+ Money.new(amount, currency)
245
+ end
246
+
247
+ # Converts a Float into a Money object treating the +value+
248
+ # as dollars and converting them to the corresponding cents value,
249
+ # according to +currency+ subunit property,
250
+ # before instantiating the Money object.
251
+ #
252
+ # Behind the scenes, this method relies on Money.from_bigdecimal
253
+ # to avoid problems with floating point precision.
254
+ #
255
+ # @param [Float] value The money amount, in dollars.
256
+ # @param [Currency, String, Symbol] currency The currency format.
257
+ #
258
+ # @return [Money]
259
+ #
260
+ # @example
261
+ # Money.from_float(100.0)
262
+ # #=> #<Money @cents=10000 @currency="USD">
263
+ # Money.from_float(100.0, "USD")
264
+ # #=> #<Money @cents=10000 @currency="USD">
265
+ # Money.from_float(100.0, "EUR")
266
+ # #=> #<Money @cents=10000 @currency="EUR">
267
+ # Money.from_float(100.0, "BHD")
268
+ # #=> #<Money @cents=100 @currency="BHD">
269
+ #
270
+ # @see Float#to_money
271
+ # @see Money.from_numeric
272
+ #
273
+ def self.from_float(value, currency = Money.default_currency)
274
+ from_bigdecimal(BigDecimal.new(value.to_s), currency)
275
+ end
276
+
277
+ # Converts a BigDecimal into a Money object treating the +value+
278
+ # as dollars and converting them to the corresponding cents value,
279
+ # according to +currency+ subunit property,
280
+ # before instantiating the Money object.
281
+ #
282
+ # @param [BigDecimal] value The money amount, in dollars.
283
+ # @param [Currency, String, Symbol] currency The currency format.
284
+ #
285
+ # @return [Money]
286
+ #
287
+ # @example
288
+ # Money.from_bigdecimal(BigDecimal.new("100")
289
+ # #=> #<Money @cents=10000 @currency="USD">
290
+ # Money.from_bigdecimal(BigDecimal.new("100", "USD")
291
+ # #=> #<Money @cents=10000 @currency="USD">
292
+ # Money.from_bigdecimal(BigDecimal.new("100", "EUR")
293
+ # #=> #<Money @cents=10000 @currency="EUR">
294
+ # Money.from_bigdecimal(BigDecimal.new("100", "BHD")
295
+ # #=> #<Money @cents=100 @currency="BHD">
296
+ #
297
+ # @see BigDecimal#to_money
298
+ # @see Money.from_numeric
299
+ #
300
+ def self.from_bigdecimal(value, currency = Money.default_currency)
301
+ currency = Money::Currency.wrap(currency)
302
+ amount = value * currency.subunit_to_unit
303
+ Money.new(amount.fix, currency)
304
+ end
305
+
306
+ # Converts a Numeric value into a Money object treating the +value+
307
+ # as dollars and converting them to the corresponding cents value,
308
+ # according to +currency+ subunit property,
309
+ # before instantiating the Money object.
310
+ #
311
+ # This method relies on various +Money.from_*+ methods
312
+ # and tries to forwards the call to the most appropriate method
313
+ # in order to reduce computation effort.
314
+ # For instance, if +value+ is an Integer, this method calls
315
+ # {Money.from_fixnum} instead of using the default
316
+ # {Money.from_bigdecimal} which adds the overload to converts
317
+ # the value into a slower BigDecimal instance.
318
+ #
319
+ # @param [Numeric] value The money amount, in dollars.
320
+ # @param [Currency, String, Symbol] currency The currency format.
321
+ #
322
+ # @return [Money]
323
+ #
324
+ # @raise +ArgumentError+ Unless +value+ is a supported type.
325
+ #
326
+ # @example
327
+ # Money.from_numeric(100)
328
+ # #=> #<Money @cents=10000 @currency="USD">
329
+ # Money.from_numeric(100.00)
330
+ # #=> #<Money @cents=10000 @currency="USD">
331
+ # Money.from_numeric("100")
332
+ # #=> ArgumentError
333
+ #
334
+ # @see Numeric#to_money
335
+ # @see Money.from_fixnum
336
+ # @see Money.from_float
337
+ # @see Money.from_bigdecimal
338
+ #
339
+ def self.from_numeric(value, currency = Money.default_currency)
340
+ case value
341
+ when Fixnum
342
+ from_fixnum(value, currency)
343
+ when Numeric
344
+ from_bigdecimal(BigDecimal.new(value.to_s), currency)
345
+ else
346
+ raise ArgumentError, "`value' should be a Numeric object"
347
+ end
348
+ end
349
+
350
+
104
351
  # Adds a new exchange rate to the default bank and return the rate.
105
352
  #
106
353
  # @param [Currency, String, Symbol] from_currency Currency to exchange from.
107
- # @param [Currency, String, Symbol[ to_currency Currency to exchange to.
354
+ # @param [Currency, String, Symbol] to_currency Currency to exchange to.
108
355
  # @param [Numeric] rate Rate to exchange with.
109
356
  #
110
357
  # @return [Numeric]
@@ -115,19 +362,30 @@ class Money
115
362
  Money.default_bank.add_rate(from_currency, to_currency, rate)
116
363
  end
117
364
 
118
- # Creates a new money object. Alternatively you can use the convenience
119
- # methods like +Money#ca_dollar+ and +Money#us_dollar+
365
+
366
+ # Creates a new Money object of +cents+ value in cents,
367
+ # with given +currency+.
120
368
  #
121
- # @param [Integer] cents The cents value.
122
- # @param [optional, Currency, String, Symbol] currency The currency format.
123
- # @param [optional, Money::Bank::*] bank The exchange bank to use.
369
+ # Alternatively you can use the convenience
370
+ # methods like {Money.ca_dollar} and {Money.us_dollar}.
371
+ #
372
+ # @param [Integer] cents The money amount, in cents.
373
+ # @param [Currency, String, Symbol] currency The currency format.
374
+ # @param [Money::Bank::*] bank The exchange bank to use.
124
375
  #
125
376
  # @return [Money]
126
377
  #
127
378
  # @example
128
- # Money.new(100) #=> #<Money @cents=100>
129
- def initialize(cents, currency = Money.default_currency,
130
- bank = Money.default_bank)
379
+ # Money.new(100)
380
+ # #=> #<Money @cents=100 @currency="USD">
381
+ # Money.new(100, "USD")
382
+ # #=> #<Money @cents=100 @currency="USD">
383
+ # Money.new(100, "EUR")
384
+ # #=> #<Money @cents=100 @currency="EUR">
385
+ #
386
+ # @see Money.new_with_dollars
387
+ #
388
+ def initialize(cents, currency = Money.default_currency, bank = Money.default_bank)
131
389
  @cents = cents.round
132
390
  if currency.is_a?(Hash)
133
391
  # Earlier versions of Money wrongly documented the constructor as being able
@@ -136,6 +394,8 @@ class Money
136
394
  # Money.new(50, :currency => "USD")
137
395
  #
138
396
  # We retain compatibility here.
397
+ Money.deprecate "Passing :currency as option is deprecated and will be removed in v3.5.0. " +
398
+ "Please use `Money.new('#{cents}'#{currency[:currency].nil? ? "" : ", '#{currency[:currency]}'"})'"
139
399
  @currency = Currency.wrap(currency[:currency] || Money.default_currency)
140
400
  else
141
401
  @currency = Currency.wrap(currency)
@@ -143,6 +403,22 @@ class Money
143
403
  @bank = bank
144
404
  end
145
405
 
406
+ # Returns the value of the money in dollars,
407
+ # instead of in cents.
408
+ #
409
+ # @return [Float]
410
+ #
411
+ # @example
412
+ # Money.new(100).dollars # => 1.0
413
+ # Money.new_with_dollars(1).dollar # => 1.0
414
+ #
415
+ # @see #to_f
416
+ # @see #cents
417
+ #
418
+ def dollars
419
+ to_f
420
+ end
421
+
146
422
  # Return string representation of currency object
147
423
  #
148
424
  # @return [String]
@@ -222,7 +498,7 @@ class Money
222
498
  #
223
499
  # @return [-1, 0, 1]
224
500
  #
225
- # @raise +ArgumentError+
501
+ # @raise [ArgumentError]
226
502
  #
227
503
  # @example
228
504
  # Money.new(100) <=> 99 #=> 1
@@ -237,7 +513,7 @@ class Money
237
513
  cents <=> other_money.exchange_to(currency).cents
238
514
  end
239
515
  else
240
- raise ArgumentError, "comparison of #{self.class} with #{other_money.inspect} failed"
516
+ raise ArgumentError, "Comparison of #{self.class} with #{other_money.inspect} failed"
241
517
  end
242
518
  end
243
519
 
@@ -281,51 +557,63 @@ class Money
281
557
  # Multiplies the monetary value with the given number and returns a new
282
558
  # +Money+ object with this monetary value and the same currency.
283
559
  #
284
- # Multiplying with another Money object is currently not supported.
560
+ # Note that you can't multiply a Money object by an other +Money+ object.
285
561
  #
286
- # @param [Fixnum] fixnum Number to multiply by.
562
+ # @param [Numeric] value Number to multiply by.
287
563
  #
288
- # @return [Money]
564
+ # @return [Money] The resulting money.
565
+ #
566
+ # @raise [ArgumentError] If +value+ is a Money instance.
289
567
  #
290
568
  # @example
291
569
  # Money.new(100) * 2 #=> #<Money @cents=200>
292
- def *(fixnum)
293
- Money.new(cents * fixnum, currency)
570
+ #
571
+ def *(value)
572
+ if value.is_a?(Money)
573
+ raise ArgumentError, "Can't multiply a Money by a Money"
574
+ else
575
+ Money.new(cents * value, currency)
576
+ end
294
577
  end
295
578
 
296
579
  # Divides the monetary value with the given number and returns a new +Money+
297
- # object with this monetary value and the same currency. Can also divide by
298
- # another +Money+ object to get a ratio. +Money/Numeric+ returns +Money+.
299
- # +Money/Money+ returns +Float+
580
+ # object with this monetary value and the same currency.
581
+ # Can also divide by another +Money+ object to get a ratio.
582
+ #
583
+ # +Money/Numeric+ returns +Money+. +Money/Money+ returns +Float+.
300
584
  #
301
- # @param [Money, Numeric] val Number to divide by.
585
+ # @param [Money, Numeric] value Number to divide by.
302
586
  #
303
- # @return [Money, Float]
587
+ # @return [Money] The resulting money if you divide Money by a number.
588
+ # @return [Float] The resulting number if you divide Money by a Money.
304
589
  #
305
590
  # @example
306
591
  # Money.new(100) / 10 #=> #<Money @cents=10>
307
592
  # Money.new(100) / Money.new(10) #=> 10.0
308
- def /(val)
309
- if val.is_a?(Money)
310
- if currency == val.currency
311
- cents / val.cents.to_f
593
+ #
594
+ def /(value)
595
+ if value.is_a?(Money)
596
+ if currency == value.currency
597
+ (cents / BigDecimal.new(value.cents.to_s)).to_f
312
598
  else
313
- cents / val.exchange_to(currency).cents.to_f
599
+ (cents / BigDecimal(value.exchange_to(currency).cents.to_s)).to_f
314
600
  end
315
601
  else
316
- Money.new(cents / val, currency)
602
+ Money.new(cents / value, currency)
317
603
  end
318
604
  end
319
605
 
320
606
  # Synonym for +#/+.
321
607
  #
322
- # @param [Money, Numeric] val Number to divide by.
608
+ # @param [Money, Numeric] value Number to divide by.
323
609
  #
324
- # @return [Money, Float]
610
+ # @return [Money] The resulting money if you divide Money by a number.
611
+ # @return [Float] The resulting number if you divide Money by a Money.
325
612
  #
326
613
  # @see #/
327
- def div(val)
328
- self / val
614
+ #
615
+ def div(value)
616
+ self / value
329
617
  end
330
618
 
331
619
  # Divide money by money or fixnum and return array containing quotient and
@@ -569,9 +857,9 @@ class Money
569
857
  end
570
858
 
571
859
  if rules[:no_cents] or currency.subunit_to_unit == 1
572
- formatted = sprintf("#{symbol_value}%d", cents.to_f / currency.subunit_to_unit)
860
+ formatted = sprintf("#{symbol_value}%d", self.to_f)
573
861
  else
574
- formatted = sprintf("#{symbol_value}%.2f", cents.to_f / currency.subunit_to_unit)
862
+ formatted = sprintf("#{symbol_value}%.2f", self.to_f)
575
863
  end
576
864
 
577
865
  delimiter_value = delimiter
@@ -585,7 +873,7 @@ class Money
585
873
  end
586
874
 
587
875
  # Apply delimiter
588
- formatted.gsub!(/(\d)(?=\d{3}+(?:\.|$))(\d{3}\..*)?/, "\\1#{delimiter_value}\\2")
876
+ formatted.gsub!(/(\d)(?=(?:\d{3})+(?:\.|$))(\d{3}\..*)?/, "\\1#{delimiter_value}\\2")
589
877
 
590
878
  separator_value = separator
591
879
  # Determine separator
@@ -613,7 +901,7 @@ class Money
613
901
  # Money.ca_dollar(100).to_s #=> "1.00"
614
902
  def to_s
615
903
  return sprintf("%d", cents) if currency.subunit_to_unit == 1
616
- sprintf("%.2f", cents.to_f / currency.subunit_to_unit)
904
+ sprintf("%.2f", self.to_f)
617
905
  end
618
906
 
619
907
  # Return the amount of money as a float. Floating points cannot guarantee
@@ -626,7 +914,7 @@ class Money
626
914
  # @example
627
915
  # Money.us_dollar(100).to_f => 1.0
628
916
  def to_f
629
- cents.to_f / currency.subunit_to_unit
917
+ (BigDecimal.new(cents.to_s) / currency.subunit_to_unit).to_f
630
918
  end
631
919
 
632
920
  # Receive the amount of this money object in another Currency.
@@ -694,10 +982,14 @@ class Money
694
982
  #
695
983
  # @return [Hash]
696
984
  def normalize_formatting_rules(rules)
697
- if rules.size == 1
985
+ if rules.size == 0
986
+ rules = {}
987
+ elsif rules.size == 1
698
988
  rules = rules.pop
699
989
  rules = { rules => true } if rules.is_a?(Symbol)
700
990
  else
991
+ Money.deprecate "Passing options as parameters is deprecated and will be removed in v3.5.0. " +
992
+ "Please use `#format(#{rules.map { |r| ":#{r} => true" }.join(", ") })'"
701
993
  rules = rules.inject({}) do |h,s|
702
994
  h[s] = true
703
995
  h
@@ -705,4 +997,118 @@ class Money
705
997
  end
706
998
  rules
707
999
  end
1000
+
1001
+ # Takes a number string and attempts to massage out the number.
1002
+ #
1003
+ # @param [String] input The string containing a potential number.
1004
+ #
1005
+ # @return [Integer]
1006
+ #
1007
+ def self.extract_cents(input, currency = Money.default_currency)
1008
+ # remove anything that's not a number, potential delimiter, or minus sign
1009
+ num = input.gsub(/[^\d|\.|,|\'|\s|\-]/, '').strip
1010
+
1011
+ # set a boolean flag for if the number is negative or not
1012
+ negative = num.split(//).first == "-"
1013
+
1014
+ # if negative, remove the minus sign from the number
1015
+ num = num.gsub(/^-/, '') if negative
1016
+
1017
+ # gather all separators within the result number
1018
+ used_separators = num.scan /[^\d]/
1019
+
1020
+ # determine the number of unique separators within the number
1021
+ #
1022
+ # e.g.
1023
+ # $1,234,567.89 would return 2 (, and .)
1024
+ # $125,00 would return 1
1025
+ # $199 would return 0
1026
+ # $1 234,567.89 would raise an error (separators are space, comma, and period)
1027
+ case used_separators.uniq.length
1028
+ # no separator or delimiter; major (dollars) is the number, and minor (cents) is 0
1029
+ when 0 then major, minor = num, 0
1030
+
1031
+ # two separators, so we know the last item in this array is the
1032
+ # major/minor delimiter and the rest are separators
1033
+ when 2
1034
+ separator, delimiter = used_separators.uniq
1035
+ # remove all separators, split on the delimiter
1036
+ major, minor = num.gsub(separator, '').split(delimiter)
1037
+ min = 0 unless min
1038
+ when 1
1039
+ # we can't determine if the comma or period is supposed to be a separator or a delimiter
1040
+ # e.g.
1041
+ # 1,00 - comma is a delimiter
1042
+ # 1.000 - period is a delimiter
1043
+ # 1,000 - comma is a separator
1044
+ # 1,000,000 - comma is a separator
1045
+ # 10000,00 - comma is a delimiter
1046
+ # 1000,000 - comma is a delimiter
1047
+
1048
+ # assign first separator for reusability
1049
+ separator = used_separators.first
1050
+
1051
+ # separator is used as a separator when there are multiple instances, always
1052
+ if num.scan(separator).length > 1 # multiple matches; treat as separator
1053
+ major, minor = num.gsub(separator, ''), 0
1054
+ else
1055
+ # ex: 1,000 - 1.0000 - 10001.000
1056
+ # split number into possible major (dollars) and minor (cents) values
1057
+ possible_major, possible_minor = num.split(separator)
1058
+ possible_major ||= "0"
1059
+ possible_minor ||= "00"
1060
+
1061
+ # if the minor (cents) length isn't 3, assign major/minor from the possibles
1062
+ # e.g.
1063
+ # 1,00 => 1.00
1064
+ # 1.0000 => 1.00
1065
+ # 1.2 => 1.20
1066
+ if possible_minor.length != 3 # delimiter
1067
+ major, minor = possible_major, possible_minor
1068
+ else
1069
+ # minor length is three
1070
+ # let's try to figure out intent of the delimiter
1071
+
1072
+ # the major length is greater than three, which means
1073
+ # the comma or period is used as a delimiter
1074
+ # e.g.
1075
+ # 1000,000
1076
+ # 100000,000
1077
+ if possible_major.length > 3
1078
+ major, minor = possible_major, possible_minor
1079
+ else
1080
+ # number is in format ###{sep}### or ##{sep}### or #{sep}###
1081
+ # handle as , is sep, . is delimiter
1082
+ if separator == '.'
1083
+ major, minor = possible_major, possible_minor
1084
+ else
1085
+ major, minor = "#{possible_major}#{possible_minor}", 0
1086
+ end
1087
+ end
1088
+ end
1089
+ end
1090
+ else
1091
+ # TODO: ParseError
1092
+ raise ArgumentError, "Invalid currency amount"
1093
+ end
1094
+
1095
+ # build the string based on major/minor since separator/delimiters have been removed
1096
+ # avoiding floating point arithmetic here to ensure accuracy
1097
+ cents = (major.to_i * currency.subunit_to_unit)
1098
+ # add the minor number as well. this may have any number of digits,
1099
+ # so we treat minor as a string and truncate or right-fill it with zeroes
1100
+ # until it becomes a two-digit number string, which we add to cents.
1101
+ minor = minor.to_s
1102
+ truncated_minor = minor[0..1]
1103
+ truncated_minor << "0" * (2 - truncated_minor.size) if truncated_minor.size < 2
1104
+ cents += truncated_minor.to_i
1105
+ # respect rounding rules
1106
+ if minor.size >= 3 && minor[2..2].to_i >= 5
1107
+ cents += 1
1108
+ end
1109
+
1110
+ # if negative, multiply by -1; otherwise, return positive cents
1111
+ negative ? cents * -1 : cents
1112
+ end
1113
+
708
1114
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: money
3
3
  version: !ruby/object:Gem::Version
4
- hash: 3
4
+ hash: 9
5
5
  prerelease: false
6
6
  segments:
7
7
  - 3
8
8
  - 1
9
- - 0
10
- version: 3.1.0
9
+ - 5
10
+ version: 3.1.5
11
11
  platform: ruby
12
12
  authors:
13
13
  - Tobias Luetke
@@ -19,7 +19,7 @@ autorequire:
19
19
  bindir: bin
20
20
  cert_chain: []
21
21
 
22
- date: 2010-09-13 00:00:00 -04:00
22
+ date: 2010-10-08 00:00:00 -04:00
23
23
  default_executable:
24
24
  dependencies:
25
25
  - !ruby/object:Gem::Dependency
@@ -109,12 +109,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
109
109
  requirements:
110
110
  - - ">="
111
111
  - !ruby/object:Gem::Version
112
- hash: 21
112
+ hash: 23
113
113
  segments:
114
114
  - 1
115
115
  - 3
116
- - 7
117
- version: 1.3.7
116
+ - 6
117
+ version: 1.3.6
118
118
  requirements:
119
119
  - json if you plan to import/export rates formatted as json
120
120
  rubyforge_project: money