money 3.1.0 → 3.1.5

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/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