money 4.0.2 → 5.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## 5.0.0
4
+ - Minor bugfix - incorrect use of character range resulted in
5
+ botched results for Money::Parsing#extract_cents (GH-162)
6
+ - Money::Currency::TABLE removed. Use Money::Currency.register to add
7
+ additional currencies (GH-143)
8
+ - Fix rounding error in Numeric.to_money (GH-145)
9
+ - Allow on-the-fly calculation of decimal places if not known already
10
+ (GH-146,GH-147,GH-148)
11
+ - Move Euro symbol ahead of amount (GH-151)
12
+ - Changed settings for Polish currency (GH-152)
13
+ - Fall back to symbol if there is no html_entity present (GH-153)
14
+ - Optionally Allow parsing of money values prefixed by symbols in key
15
+ currencies (GH-155)
16
+ - Fix bug where rates exported to a file from VariableExchange leave the File
17
+ object open (GH-154)
18
+ - Added Money#positive? and Money#negative? methods (GH-157)
19
+ - Fix format function output for custom currencies (GH-156)
20
+ - Fix parsing of strings with 3 decimal digits (GH-158)
3
21
 
4
22
  ## 4.0.2
5
23
 
data/README.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  [![Build Status](http://travis-ci.org/RubyMoney/money.png)](http://travis-ci.org/RubyMoney/money)
4
4
 
5
+ ## Contributing
6
+
7
+ When contributing, please make sure to update the CHANGELOG and AUTHORS files
8
+ when you submit your pull request. Upon merging of your first pull request,
9
+ you will be given commit access to the repository.
10
+
5
11
  ## Introduction
6
12
 
7
13
  This library aids one in handling money and different currencies.
@@ -64,6 +70,12 @@ Money.new(1000, "USD") - Money.new(200, "USD") == Money.new(800, "USD")
64
70
  Money.new(1000, "USD") / 5 == Money.new(200, "USD")
65
71
  Money.new(1000, "USD") * 5 == Money.new(5000, "USD")
66
72
 
73
+ # Assumptive Currencies
74
+ Money.assume_from_symbol = true
75
+ Money.new("$100") == Money.new(10000, "USD")
76
+ Money.new("€100") == Money.new(10000, "EUR")
77
+ Money.new("£100") == Money.new(10000, "GBP")
78
+
67
79
  # Currency conversions
68
80
  some_code_to_setup_exchange_rates
69
81
  Money.new(1000, "USD").exchange_to("EUR") == Money.new(some_value, "EUR")
@@ -89,12 +101,11 @@ currency.iso_code #=> "USD"
89
101
  currency.name #=> "United States Dollar"
90
102
  ```
91
103
 
92
- To define a new `Money::Currency` simply add a new item to the
93
- `Money::Currency::TABLE` hash, where the key is the identifier for the currency
94
- object and the value is a hash containing all the currency attributes.
104
+ To define a new `Money::Currency` use `Money::Currency.register` as shown
105
+ below.
95
106
 
96
107
  ``` ruby
97
- Money::Currency::TABLE[:usd] = {
108
+ curr = {
98
109
  :priority => 1,
99
110
  :iso_code => "USD",
100
111
  :iso_numeric => "840",
@@ -105,6 +116,8 @@ object and the value is a hash containing all the currency attributes.
105
116
  :separator => ".",
106
117
  :delimiter => ","
107
118
  }
119
+
120
+ Money::Currency.register(curr)
108
121
  ```
109
122
 
110
123
  The pre-defined set of attributes includes:
@@ -154,10 +167,10 @@ def all_currencies(hash)
154
167
  hash.keys
155
168
  end
156
169
 
157
- major_currencies(Money::Currency::TABLE)
170
+ major_currencies(Money::Currency.table)
158
171
  # => [ :usd, :eur, :bgp, :cad ]
159
172
 
160
- all_currencies(Money::Currency::TABLE)
173
+ all_currencies(Money::Currency.table)
161
174
  # => [ :aed, :afn, all, ... ]
162
175
  ```
163
176
 
@@ -217,6 +230,9 @@ implementations.
217
230
 
218
231
  ## Ruby on Rails
219
232
 
233
+ To integrate money in a rails application use [money-rails](http://github.com/RubyMoney/money-rails)
234
+ gem or follow the instructions below.
235
+
220
236
  Use the `composed_of` helper to let Active Record deal with embedding the money
221
237
  object in your models. The following example requires 2 columns:
222
238
 
@@ -241,7 +257,7 @@ definition:
241
257
  ``` ruby
242
258
  composed_of :price,
243
259
  :class_name => "Money",
244
- :mapping => [%w(cents cents), %w(currency currency)],
260
+ :mapping => [%w(price_cents cents), %w(currency currency)],
245
261
  :constructor => Proc.new { |cents, currency| Money.new(cents || 0, currency || Money.default_currency) }
246
262
  ```
247
263
 
@@ -565,7 +565,7 @@
565
565
  "symbol": "€",
566
566
  "subunit": "Cent",
567
567
  "subunit_to_unit": 100,
568
- "symbol_first": false,
568
+ "symbol_first": true,
569
569
  "html_entity": "€",
570
570
  "decimal_mark": ",",
571
571
  "thousands_separator": ".",
@@ -1451,8 +1451,8 @@
1451
1451
  "subunit_to_unit": 100,
1452
1452
  "symbol_first": false,
1453
1453
  "html_entity": "",
1454
- "decimal_mark": ".",
1455
- "thousands_separator": ",",
1454
+ "decimal_mark": ",",
1455
+ "thousands_separator": " ",
1456
1456
  "iso_numeric": "985"
1457
1457
  },
1458
1458
  "pyg": {
@@ -194,7 +194,7 @@ class Money
194
194
  end
195
195
 
196
196
  unless file.nil?
197
- File.open(file, "w").write(s)
197
+ File.open(file, "w") {|f| f.write(s) }
198
198
  end
199
199
  }
200
200
  s
@@ -12,24 +12,81 @@ class Money
12
12
  # Thrown when an unknown currency is requested.
13
13
  class UnknownCurrency < StandardError; end
14
14
 
15
- # List of known currencies.
16
- #
17
- # == monetary unit
18
- # The standard unit of value of a currency, as the dollar in the United States or the peso in Mexico.
19
- # http://www.answers.com/topic/monetary-unit
20
- # == fractional monetary unit, subunit
21
- # A monetary unit that is valued at a fraction (usually one hundredth) of the basic monetary unit
22
- # http://www.answers.com/topic/fractional-monetary-unit-subunit
23
- #
24
- # See http://en.wikipedia.org/wiki/List_of_circulating_currencies and
25
- # http://search.cpan.org/~tnguyen/Locale-Currency-Format-1.28/Format.pm
15
+ class << self
26
16
 
27
- TABLE = load_currencies
17
+ # Lookup a currency with given +id+ an returns a +Currency+ instance on
18
+ # success, +nil+ otherwise.
19
+ #
20
+ # @param [String, Symbol, #to_s] id Used to look into +table+ and
21
+ # retrieve the applicable attributes.
22
+ #
23
+ # @return [Money::Currency]
24
+ #
25
+ # @example
26
+ # Money::Currency.find(:eur) #=> #<Money::Currency id: eur ...>
27
+ # Money::Currency.find(:foo) #=> nil
28
+ def find(id)
29
+ id = id.to_s.downcase.to_sym
30
+ new(id) if self.table[id]
31
+ end
28
32
 
29
- # We need a string-based validator before creating an unbounded number of symbols.
30
- # http://www.randomhacks.net/articles/2007/01/20/13-ways-of-looking-at-a-ruby-symbol#11
31
- # https://github.com/RubyMoney/money/issues/132
32
- STRINGIFIED_KEYS = TABLE.keys.map{|k| k.to_s.downcase }
33
+ # Wraps the object in a +Currency+ unless it's already a +Currency+
34
+ # object.
35
+ #
36
+ # @param [Object] object The object to attempt and wrap as a +Currency+
37
+ # object.
38
+ #
39
+ # @return [Money::Currency]
40
+ #
41
+ # @example
42
+ # c1 = Money::Currency.new(:usd)
43
+ # Money::Currency.wrap(nil) #=> nil
44
+ # Money::Currency.wrap(c1) #=> #<Money::Currency id: usd ...>
45
+ # Money::Currency.wrap("usd") #=> #<Money::Currency id: usd ...>
46
+ def wrap(object)
47
+ if object.nil?
48
+ nil
49
+ elsif object.is_a?(Currency)
50
+ object
51
+ else
52
+ Currency.new(object)
53
+ end
54
+ end
55
+
56
+ # List of known currencies.
57
+ #
58
+ # == monetary unit
59
+ # The standard unit of value of a currency, as the dollar in the United States or the peso in Mexico.
60
+ # http://www.answers.com/topic/monetary-unit
61
+ # == fractional monetary unit, subunit
62
+ # A monetary unit that is valued at a fraction (usually one hundredth) of the basic monetary unit
63
+ # http://www.answers.com/topic/fractional-monetary-unit-subunit
64
+ #
65
+ # See http://en.wikipedia.org/wiki/List_of_circulating_currencies and
66
+ # http://search.cpan.org/~tnguyen/Locale-Currency-Format-1.28/Format.pm
67
+ def table
68
+ @table ||= load_currencies
69
+ end
70
+
71
+ # We need a string-based validator before creating an unbounded number of symbols.
72
+ # http://www.randomhacks.net/articles/2007/01/20/13-ways-of-looking-at-a-ruby-symbol#11
73
+ # https://github.com/RubyMoney/money/issues/132
74
+ def stringified_keys
75
+ @stringified_keys ||= stringify_keys
76
+ end
77
+
78
+ def register(curr)
79
+ key = curr[:iso_code].downcase.to_sym
80
+ @table[key] = curr
81
+ @stringified_keys = stringify_keys
82
+ end
83
+
84
+ private
85
+
86
+ def stringify_keys
87
+ table.keys.map{|k| k.to_s.downcase}
88
+ end
89
+ end
33
90
 
34
91
  # The symbol used to identify the currency, usually the lowercase
35
92
  # +iso_code+ attribute.
@@ -94,53 +151,9 @@ class Money
94
151
  # @return [boolean]
95
152
  attr_reader :symbol_first
96
153
 
97
-
98
- class << self
99
-
100
- # Lookup a currency with given +id+ an returns a +Currency+ instance on
101
- # success, +nil+ otherwise.
102
- #
103
- # @param [String, Symbol, #to_s] id Used to look into +TABLE+ and
104
- # retrieve the applicable attributes.
105
- #
106
- # @return [Money::Currency]
107
- #
108
- # @example
109
- # Money::Currency.find(:eur) #=> #<Money::Currency id: eur ...>
110
- # Money::Currency.find(:foo) #=> nil
111
- def find(id)
112
- id = id.to_s.downcase.to_sym
113
- new(id) if self::TABLE[id]
114
- end
115
-
116
- # Wraps the object in a +Currency+ unless it's already a +Currency+
117
- # object.
118
- #
119
- # @param [Object] object The object to attempt and wrap as a +Currency+
120
- # object.
121
- #
122
- # @return [Money::Currency]
123
- #
124
- # @example
125
- # c1 = Money::Currency.new(:usd)
126
- # Money::Currency.wrap(nil) #=> nil
127
- # Money::Currency.wrap(c1) #=> #<Money::Currency id: usd ...>
128
- # Money::Currency.wrap("usd") #=> #<Money::Currency id: usd ...>
129
- def wrap(object)
130
- if object.nil?
131
- nil
132
- elsif object.is_a?(Currency)
133
- object
134
- else
135
- Currency.new(object)
136
- end
137
- end
138
- end
139
-
140
-
141
154
  # Create a new +Currency+ object.
142
155
  #
143
- # @param [String, Symbol, #to_s] id Used to look into +TABLE+ and retrieve
156
+ # @param [String, Symbol, #to_s] id Used to look into +table+ and retrieve
144
157
  # the applicable attributes.
145
158
  #
146
159
  # @return [Money::Currency]
@@ -149,10 +162,10 @@ class Money
149
162
  # Money::Currency.new(:usd) #=> #<Money::Currency id: usd ...>
150
163
  def initialize(id)
151
164
  id = id.to_s.downcase
152
- raise(UnknownCurrency, "Unknown currency `#{id}'") unless STRINGIFIED_KEYS.include?(id)
165
+ raise(UnknownCurrency, "Unknown currency `#{id}'") unless self.class.stringified_keys.include?(id)
153
166
 
154
167
  @id = id.to_sym
155
- data = TABLE[@id]
168
+ data = self.class.table[@id]
156
169
  data.each_pair do |key, value|
157
170
  instance_variable_set(:"@#{key}", value)
158
171
  end
@@ -263,18 +276,41 @@ class Money
263
276
  !!@symbol_first
264
277
  end
265
278
 
279
+ # Cache decimal places for subunit_to_unit values. Common ones pre-cached.
280
+ def self.decimal_places_cache
281
+ @decimal_places_cache ||= {
282
+ 1 => 0,
283
+ 10 => 1,
284
+ 100 => 2,
285
+ 1000 => 3
286
+ }
287
+ end
288
+
266
289
  # The number of decimal places needed.
267
290
  #
268
291
  # @return [Integer]
269
292
  def decimal_places
270
- if subunit_to_unit == 1
271
- 0
272
- elsif subunit_to_unit % 10 == 0
273
- Math.log10(subunit_to_unit).to_s.to_i
274
- else
275
- Math.log10(subunit_to_unit).to_s.to_i+1
293
+ cache = self.class.decimal_places_cache
294
+ places = cache[subunit_to_unit]
295
+ unless places
296
+ places = calculate_decimal_places(subunit_to_unit)
297
+ cache[subunit_to_unit] = places
298
+ end
299
+ places
300
+ end
301
+
302
+ # If we need to figure out how many decimal places we need we
303
+ # use repeated integer division.
304
+ def calculate_decimal_places(num)
305
+ return 0 if num == 1
306
+ i = 1
307
+ while num >= 10
308
+ num /= 10
309
+ i += 1 if num >= 10
276
310
  end
311
+ i
277
312
  end
313
+ private :calculate_decimal_places
278
314
 
279
315
  end
280
316
  end
@@ -30,7 +30,7 @@ class Money
30
30
  class << self
31
31
  # Each Money object is associated to a bank object, which is responsible
32
32
  # for currency exchange. This property allows you to specify the default
33
- # bank object. The default value for this property is an instance if
33
+ # bank object. The default value for this property is an instance of
34
34
  # +Bank::VariableExchange.+ It allows one to specify custom exchange rates.
35
35
  #
36
36
  # @return [Money::Bank::*]
@@ -47,6 +47,11 @@ class Money
47
47
  #
48
48
  # @return [true,false]
49
49
  attr_accessor :use_i18n
50
+
51
+ # Use this to enable the ability to assume the currency from a passed symbol
52
+ #
53
+ # @return [true,false]
54
+ attr_accessor :assume_from_symbol
50
55
  end
51
56
 
52
57
  # Set the default bank for creating new +Money+ objects.
@@ -58,6 +63,9 @@ class Money
58
63
  # Default to using i18n
59
64
  self.use_i18n = true
60
65
 
66
+ # Default to not using currency symbol assumptions when parsing
67
+ self.assume_from_symbol = false
68
+
61
69
  # Create a new money object with value 0.
62
70
  #
63
71
  # @param [Currency, String, Symbol] currency The currency to use.
@@ -56,6 +56,32 @@ class Money
56
56
  end
57
57
  end
58
58
 
59
+ # Test if the amount is positive. Returns +true+ if the money amount is
60
+ # greater than 0, +false+ otherwise.
61
+ #
62
+ # @return [Boolean]
63
+ #
64
+ # @example
65
+ # Money.new(1).positive? #=> true
66
+ # Money.new(0).positive? #=> false
67
+ # Money.new(-1).positive? #=> false
68
+ def positive?
69
+ cents > 0
70
+ end
71
+
72
+ # Test if the amount is negative. Returns +true+ if the money amount is
73
+ # less than 0, +false+ otherwise.
74
+ #
75
+ # @return [Boolean]
76
+ #
77
+ # @example
78
+ # Money.new(-1).negative? #=> true
79
+ # Money.new(0).negative? #=> false
80
+ # Money.new(1).negative? #=> false
81
+ def negative?
82
+ cents < 0
83
+ end
84
+
59
85
  # Returns a new Money object containing the sum of the two operands' monetary
60
86
  # values. If +other_money+ has a different currency then its monetary value
61
87
  # is automatically exchanged to this object's currency using +exchange_to+.
@@ -163,13 +163,13 @@ class Money
163
163
  ""
164
164
  end
165
165
  elsif rules[:html]
166
- currency.html_entity
166
+ currency.html_entity == '' ? currency.symbol : currency.html_entity
167
167
  else
168
168
  symbol
169
169
  end
170
170
 
171
171
  formatted = rules[:no_cents] ? "#{self.to_s.to_i}" : self.to_s
172
-
172
+
173
173
  if rules[:no_cents_if_whole] && cents % currency.subunit_to_unit == 0
174
174
  formatted = "#{self.to_s.to_i}"
175
175
  end
@@ -204,7 +204,13 @@ class Money
204
204
  end
205
205
 
206
206
  # Apply thousands_separator
207
- formatted.gsub!(/(\d)(?=(?:\d{3})+(?:[^\d]|$))/, "\\1#{thousands_separator_value}")
207
+ regexp_decimal = Regexp.escape(decimal_mark)
208
+ regexp_format = if formatted =~ /#{regexp_decimal}/
209
+ /(\d)(?=(?:\d{3})+(?:#{regexp_decimal}))/
210
+ else
211
+ /(\d)(?=(?:\d{3})+(?:[^\d]{1}|$))/
212
+ end
213
+ formatted.gsub!(regexp_format, "\\1#{thousands_separator_value}")
208
214
 
209
215
  if rules[:with_currency]
210
216
  formatted << " "
@@ -1,3 +1,5 @@
1
+ #encoding: utf-8
2
+
1
3
  class Money
2
4
  module Parsing
3
5
  def self.included(base)
@@ -32,11 +34,21 @@ class Money
32
34
  # @see Money.from_string
33
35
  #
34
36
  def parse(input, currency = nil)
35
- i = input.to_s
37
+ i = input.to_s.strip
38
+
39
+ # raise Money::Currency.table.collect{|c| c[1][:symbol]}.inspect
36
40
 
37
- # Get the currency.
38
- m = i.scan /([A-Z]{2,3})/
39
- c = m[0] ? m[0][0] : nil
41
+ # Check the first character for a currency symbol, alternatively get it
42
+ # from the stated currency string
43
+ c = if Money.assume_from_symbol && i =~ /^(\$|€|£)/
44
+ case i
45
+ when /^$/ then "USD"
46
+ when /^€/ then "EUR"
47
+ when /^£/ then "GBP"
48
+ end
49
+ else
50
+ i[/[A-Z]{2,3}/]
51
+ end
40
52
 
41
53
  # check that currency passed and embedded currency are the same,
42
54
  # and negotiate the final currency
@@ -172,7 +184,7 @@ class Money
172
184
  def from_bigdecimal(value, currency = Money.default_currency)
173
185
  currency = Money::Currency.wrap(currency)
174
186
  amount = value * currency.subunit_to_unit
175
- new(amount.fix, currency)
187
+ new(amount.round, currency)
176
188
  end
177
189
 
178
190
  # Converts a Numeric value into a Money object treating the +value+
@@ -227,11 +239,14 @@ class Money
227
239
  #
228
240
  def extract_cents(input, currency = Money.default_currency)
229
241
  # remove anything that's not a number, potential thousands_separator, or minus sign
230
- num = input.gsub(/[^\d|\.|,|\'|\-]/, '').strip
242
+ num = input.gsub(/[^\d.,'-]/, '')
231
243
 
232
244
  # set a boolean flag for if the number is negative or not
233
245
  negative = num =~ /^-|-$/ ? true : false
234
246
 
247
+ # decimal mark character
248
+ decimal_char = currency.decimal_mark
249
+
235
250
  # if negative, remove the minus sign from the number
236
251
  # if it's not negative, the hyphen makes the value invalid
237
252
  if negative
@@ -279,41 +294,47 @@ class Money
279
294
  # assign first decimal_mark for reusability
280
295
  decimal_mark = used_decimal_marks.first
281
296
 
282
- # decimal_mark is used as a decimal_mark when there are multiple instances, always
283
- if num.scan(decimal_mark).length > 1 # multiple matches; treat as decimal_mark
284
- major, minor = num.gsub(decimal_mark, ''), 0
285
- else
286
- # ex: 1,000 - 1.0000 - 10001.000
287
- # split number into possible major (dollars) and minor (cents) values
288
- possible_major, possible_minor = num.split(decimal_mark)
289
- possible_major ||= "0"
290
- possible_minor ||= "00"
297
+ # When we have identified the decimal mark character
298
+ if decimal_char == decimal_mark
299
+ major, minor = num.split(decimal_char)
291
300
 
292
- # if the minor (cents) length isn't 3, assign major/minor from the possibles
293
- # e.g.
294
- # 1,00 => 1.00
295
- # 1.0000 => 1.00
296
- # 1.2 => 1.20
297
- if possible_minor.length != 3 # thousands_separator
298
- major, minor = possible_major, possible_minor
301
+ else
302
+ # decimal_mark is used as a decimal_mark when there are multiple instances, always
303
+ if num.scan(decimal_mark).length > 1 # multiple matches; treat as decimal_mark
304
+ major, minor = num.gsub(decimal_mark, ''), 0
299
305
  else
300
- # minor length is three
301
- # let's try to figure out intent of the thousands_separator
306
+ # ex: 1,000 - 1.0000 - 10001.000
307
+ # split number into possible major (dollars) and minor (cents) values
308
+ possible_major, possible_minor = num.split(decimal_mark)
309
+ possible_major ||= "0"
310
+ possible_minor ||= "00"
302
311
 
303
- # the major length is greater than three, which means
304
- # the comma or period is used as a thousands_separator
312
+ # if the minor (cents) length isn't 3, assign major/minor from the possibles
305
313
  # e.g.
306
- # 1000,000
307
- # 100000,000
308
- if possible_major.length > 3
314
+ # 1,00 => 1.00
315
+ # 1.0000 => 1.00
316
+ # 1.2 => 1.20
317
+ if possible_minor.length != 3 # thousands_separator
309
318
  major, minor = possible_major, possible_minor
310
319
  else
311
- # number is in format ###{sep}### or ##{sep}### or #{sep}###
312
- # handle as , is sep, . is thousands_separator
313
- if decimal_mark == '.'
320
+ # minor length is three
321
+ # let's try to figure out intent of the thousands_separator
322
+
323
+ # the major length is greater than three, which means
324
+ # the comma or period is used as a thousands_separator
325
+ # e.g.
326
+ # 1000,000
327
+ # 100000,000
328
+ if possible_major.length > 3
314
329
  major, minor = possible_major, possible_minor
315
330
  else
316
- major, minor = "#{possible_major}#{possible_minor}", 0
331
+ # number is in format ###{sep}### or ##{sep}### or #{sep}###
332
+ # handle as , is sep, . is thousands_separator
333
+ if decimal_mark == '.'
334
+ major, minor = possible_major, possible_minor
335
+ else
336
+ major, minor = "#{possible_major}#{possible_minor}", 0
337
+ end
317
338
  end
318
339
  end
319
340
  end
@@ -344,7 +365,6 @@ class Money
344
365
  # if negative, multiply by -1; otherwise, return positive cents
345
366
  negative ? cents * -1 : cents
346
367
  end
347
-
348
368
  end
349
369
  end
350
370
  end
@@ -1,7 +1,7 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
  Gem::Specification.new do |s|
3
3
  s.name = "money"
4
- s.version = "4.0.2"
4
+ s.version = "5.0.0.rc1"
5
5
  s.platform = Gem::Platform::RUBY
6
6
  s.authors = ["Tobias Luetke", "Hongli Lai", "Jeremy McNevin",
7
7
  "Shane Emmons", "Simone Carletti"]
@@ -146,7 +146,7 @@ describe Money::Bank::VariableExchange do
146
146
  context "with :file provided" do
147
147
  it "writes rates to file" do
148
148
  f = mock('IO')
149
- File.should_receive(:open).with('null', 'w').and_return(f)
149
+ File.should_receive(:open).with('null', 'w').and_yield(f)
150
150
  f.should_receive(:write).with(@rates.to_json)
151
151
 
152
152
  subject.export_rates(:json, 'null')
@@ -4,18 +4,17 @@ require "spec_helper"
4
4
 
5
5
  describe Money::Currency do
6
6
 
7
+ FOO = '{ "priority": 1, "iso_code": "FOO", "iso_numeric": "840", "name": "United States Dollar", "symbol": "$", "subunit": "Cent", "subunit_to_unit": 450, "symbol_first": true, "html_entity": "$", "decimal_mark": ".", "thousands_separator": "," }'
8
+
7
9
  describe ".find" do
8
10
  it "returns currency matching given id" do
9
- with_custom_definitions do
10
- Money::Currency::TABLE[:usd] = JSON.parse(%Q({ "priority": 1, "iso_code": "USD", "iso_numeric": "840", "name": "United States Dollar", "symbol": "$", "subunit": "Cent", "subunit_to_unit": 100, "symbol_first": true, "html_entity": "$", "decimal_mark": ".", "thousands_separator": "," }))
11
- Money::Currency::TABLE[:eur] = JSON.parse(%Q({ "priority": 2, "iso_code": "EUR", "iso_numeric": "978", "name": "Euro", "symbol": "€", "subunit": "Cent", "subunit_to_unit": 100, "symbol_first": false, "html_entity": "&#x20AC;", "decimal_mark": ",", "thousands_separator": "." }))
11
+ Money::Currency.register(JSON.parse(FOO, :symbolize_names => true))
12
12
 
13
- expected = Money::Currency.new(:eur)
14
- Money::Currency.find(:eur).should == expected
15
- Money::Currency.find(:EUR).should == expected
16
- Money::Currency.find("eur").should == expected
17
- Money::Currency.find("EUR").should == expected
18
- end
13
+ expected = Money::Currency.new(:foo)
14
+ Money::Currency.find(:foo).should == expected
15
+ Money::Currency.find(:FOO).should == expected
16
+ Money::Currency.find("foo").should == expected
17
+ Money::Currency.find("FOO").should == expected
19
18
  end
20
19
 
21
20
  it "returns nil unless currency matching given id" do
@@ -123,17 +122,15 @@ describe Money::Currency do
123
122
  end
124
123
  end
125
124
 
125
+ describe "#decimal_places" do
126
+ it "proper places for known currency" do
127
+ Money::Currency.new(:mro).decimal_places == 1
128
+ Money::Currency.new(:usd).decimal_places == 2
129
+ end
126
130
 
127
- def with_custom_definitions(&block)
128
- begin
129
- old = Money::Currency::TABLE.dup
130
- Money::Currency::TABLE.clear
131
- yield
132
- ensure
133
- silence_warnings do
134
- Money::Currency.const_set("TABLE", old)
135
- end
131
+ it "proper places for custom currency" do
132
+ Money::Currency.register(JSON.parse(FOO, :symbolize_names => true))
133
+ Money::Currency.new(:foo).decimal_places == 3
136
134
  end
137
135
  end
138
-
139
136
  end
@@ -164,6 +164,34 @@ describe Money do
164
164
  end
165
165
  end
166
166
 
167
+ describe "#positive?" do
168
+ it "returns true if the amount is greater than 0" do
169
+ Money.new(1).should be_positive
170
+ end
171
+
172
+ it "returns false if the amount is 0" do
173
+ Money.new(0).should_not be_positive
174
+ end
175
+
176
+ it "returns false if the amount is negative" do
177
+ Money.new(-1).should_not be_positive
178
+ end
179
+ end
180
+
181
+ describe "#negative?" do
182
+ it "returns true if the amount is less than 0" do
183
+ Money.new(-1).should be_negative
184
+ end
185
+
186
+ it "returns false if the amount is 0" do
187
+ Money.new(0).should_not be_negative
188
+ end
189
+
190
+ it "returns false if the amount is greater than 0" do
191
+ Money.new(1).should_not be_negative
192
+ end
193
+ end
194
+
167
195
  describe "#+" do
168
196
  it "adds other amount to current amount (same currency)" do
169
197
  (Money.new(10_00, "USD") + Money.new(90, "USD")).should == Money.new(10_90, "USD")
@@ -4,6 +4,9 @@ require "spec_helper"
4
4
 
5
5
  describe Money, "formatting" do
6
6
 
7
+ BAR = '{ "priority": 1, "iso_code": "BAR", "iso_numeric": "840", "name": "Dollar with 4 decimal places", "symbol": "$", "subunit": "Cent", "subunit_to_unit": 10000, "symbol_first": true, "html_entity": "$", "decimal_mark": ".", "thousands_separator": "," }'
8
+ EU4 = '{ "priority": 1, "iso_code": "EU4", "iso_numeric": "841", "name": "Euro with 4 decimal places", "symbol": "€", "subunit": "Cent", "subunit_to_unit": 10000, "symbol_first": true, "html_entity": "€", "decimal_mark": ",", "thousands_separator": "." }'
9
+
7
10
  context "without i18n" do
8
11
  subject { Money.empty("USD") }
9
12
 
@@ -117,7 +120,7 @@ describe Money, "formatting" do
117
120
  one_thousand["CNY"].should == "¥1,000.00"
118
121
 
119
122
  # Euro
120
- one_thousand["EUR"].should == "1.000,00"
123
+ one_thousand["EUR"].should == "1.000,00"
121
124
 
122
125
  # Rupees
123
126
  one_thousand["INR"].should == "₨1,000.00"
@@ -139,8 +142,8 @@ describe Money, "formatting" do
139
142
  end
140
143
 
141
144
  it "inserts thousands separator into the result if the amount is sufficiently large and the currency symbol is at the end" do
142
- Money.euro(1_234_567_12).format.should == "1.234.567,12"
143
- Money.euro(1_234_567_12).format(:no_cents => true).should == "1.234.567"
145
+ Money.euro(1_234_567_12).format.should == "1.234.567,12"
146
+ Money.euro(1_234_567_12).format(:no_cents => true).should == "1.234.567"
144
147
  end
145
148
 
146
149
  describe ":with_currency option" do
@@ -216,7 +219,7 @@ describe Money, "formatting" do
216
219
  one["CNY"].should == "¥1.00"
217
220
 
218
221
  # Euro
219
- one["EUR"].should == "1,00"
222
+ one["EUR"].should == "1,00"
220
223
 
221
224
  # Rupees
222
225
  one["INR"].should == "₨1.00"
@@ -235,12 +238,12 @@ describe Money, "formatting" do
235
238
  specify "(:symbol => true) returns $ when currency code is not recognized" do
236
239
  currency = Money::Currency.new("EUR")
237
240
  currency.should_receive(:symbol).and_return(nil)
238
- Money.new(100, currency).format(:symbol => true).should == "1,00 ¤"
241
+ Money.new(100, currency).format(:symbol => true).should == "¤1,00"
239
242
  end
240
243
 
241
244
  specify "(:symbol => some non-Boolean value that evaluates to true) returns symbol based on the given currency code" do
242
245
  Money.new(100, "GBP").format(:symbol => true).should == "£1.00"
243
- Money.new(100, "EUR").format(:symbol => true).should == "1,00"
246
+ Money.new(100, "EUR").format(:symbol => true).should == "1,00"
244
247
  Money.new(100, "SEK").format(:symbol => true).should == "kr1.00"
245
248
  end
246
249
 
@@ -259,7 +262,7 @@ describe Money, "formatting" do
259
262
  money.format.should == "£1.00"
260
263
 
261
264
  money = Money.new(100, "EUR")
262
- money.format.should == "1,00"
265
+ money.format.should == "1,00"
263
266
  end
264
267
  end
265
268
 
@@ -310,6 +313,11 @@ describe Money, "formatting" do
310
313
  string = Money.ca_dollar(570).format(:html => true, :with_currency => true)
311
314
  string.should == "$5.70 <span class=\"currency\">CAD</span>"
312
315
  end
316
+
317
+ specify "should fallback to symbol if entity is not available" do
318
+ string = Money.new(570, 'DKK').format(:html => true)
319
+ string.should == "5,70 kr"
320
+ end
313
321
  end
314
322
 
315
323
  describe ":symbol_position option" do
@@ -398,5 +406,41 @@ describe Money, "formatting" do
398
406
  end
399
407
  end
400
408
 
409
+ context "custom currencies with 4 decimal places" do
410
+ before :each do
411
+ Money::Currency.register(JSON.parse(BAR, :symbolize_names => true))
412
+ Money::Currency.register(JSON.parse(EU4, :symbolize_names => true))
413
+ end
414
+
415
+ it "respects custom subunit to unit, decimal and thousands separator" do
416
+ Money.new(4, "BAR").format.should == "$0.0004"
417
+ Money.new(4, "EU4").format.should == "€0,0004"
418
+
419
+ Money.new(24, "BAR").format.should == "$0.0024"
420
+ Money.new(24, "EU4").format.should == "€0,0024"
421
+
422
+ Money.new(324, "BAR").format.should == "$0.0324"
423
+ Money.new(324, "EU4").format.should == "€0,0324"
424
+
425
+ Money.new(5324, "BAR").format.should == "$0.5324"
426
+ Money.new(5324, "EU4").format.should == "€0,5324"
427
+
428
+ Money.new(65324, "BAR").format.should == "$6.5324"
429
+ Money.new(65324, "EU4").format.should == "€6,5324"
430
+
431
+ Money.new(865324, "BAR").format.should == "$86.5324"
432
+ Money.new(865324, "EU4").format.should == "€86,5324"
433
+
434
+ Money.new(1865324, "BAR").format.should == "$186.5324"
435
+ Money.new(1865324, "EU4").format.should == "€186,5324"
436
+
437
+ Money.new(33310034, "BAR").format.should == "$3,331.0034"
438
+ Money.new(33310034, "EU4").format.should == "€3.331,0034"
439
+
440
+ Money.new(88833310034, "BAR").format.should == "$8,883,331.0034"
441
+ Money.new(88833310034, "EU4").format.should == "€8.883.331,0034"
442
+ end
443
+
444
+ end
401
445
  end
402
446
 
@@ -4,6 +4,9 @@ require "spec_helper"
4
4
 
5
5
  describe Money, "parsing" do
6
6
 
7
+ bar = '{ "priority": 1, "iso_code": "BAR", "iso_numeric": "840", "name": "Dollar with 4 decimal places", "symbol": "$", "subunit": "Cent", "subunit_to_unit": 10000, "symbol_first": true, "html_entity": "$", "decimal_mark": ".", "thousands_separator": "," }'
8
+ eu4 = '{ "priority": 1, "iso_code": "EU4", "iso_numeric": "841", "name": "Euro with 4 decimal places", "symbol": "€", "subunit": "Cent", "subunit_to_unit": 10000, "symbol_first": true, "html_entity": "€", "decimal_mark": ",", "thousands_separator": "." }'
9
+
7
10
  describe ".parse" do
8
11
  it "parses european-formatted inputs under 10EUR" do
9
12
  five_ninety_five = Money.new(595, 'EUR')
@@ -19,6 +22,38 @@ describe Money, "parsing" do
19
22
  Money.parse('EUR 1.111.234.567,89').should == Money.new(111123456789, 'EUR')
20
23
  end
21
24
 
25
+ describe 'currency assumption' do
26
+ context 'opted in' do
27
+ before do
28
+ Money.assume_from_symbol = true
29
+ end
30
+ it "parses formatted inputs with the currency passed as a symbol" do
31
+ Money.parse("$5.95").should == Money.new(595, 'USD')
32
+ Money.parse("€5.95").should == Money.new(595, 'EUR')
33
+ Money.parse(" €5.95 ").should == Money.new(595, 'EUR')
34
+ Money.parse("£9.99").should == Money.new(999, 'GBP')
35
+ end
36
+ it 'should assume default currency if not a recognised symbol' do
37
+ Money.parse("L9.99").should == Money.new(999, 'USD')
38
+ end
39
+ end
40
+ context 'opted out' do
41
+ before do
42
+ Money.assume_from_symbol = false
43
+ end
44
+ it "parses formatted inputs with the currency passed as a symbol but ignores the symbol" do
45
+ Money.parse("$5.95").should == Money.new(595, 'USD')
46
+ Money.parse("€5.95").should == Money.new(595, 'USD')
47
+ Money.parse(" €5.95 ").should == Money.new(595, 'USD')
48
+ Money.parse("£9.99").should == Money.new(999, 'USD')
49
+
50
+ end
51
+ end
52
+ it 'should opt out by default' do
53
+ Money.assume_from_symbol.should be_false
54
+ end
55
+ end
56
+
22
57
  it "parses USD-formatted inputs under $10" do
23
58
  five_ninety_five = Money.new(595, 'USD')
24
59
 
@@ -63,6 +98,56 @@ describe Money, "parsing" do
63
98
  it "raises ArgumentError when unable to detect polarity" do
64
99
  lambda { Money.parse('-$5.95-') }.should raise_error ArgumentError
65
100
  end
101
+
102
+ it "parses correctly strings with exactly 3 decimal digits" do
103
+ Money.parse("6,534", "EUR").should == Money.new(653, "EUR")
104
+ end
105
+
106
+ context "custom currencies with 4 decimal places" do
107
+ before :each do
108
+ Money::Currency.register(JSON.parse(bar, :symbolize_names => true))
109
+ Money::Currency.register(JSON.parse(eu4, :symbolize_names => true))
110
+ end
111
+
112
+ # String#to_money(Currency) is equivalent to Money.parse(String, Currency)
113
+ it "parses strings respecting subunit to unit, decimal and thousands separator" do
114
+ "$0.4".to_money("BAR").should == Money.new(4000, "BAR")
115
+ "€0,4".to_money("EU4").should == Money.new(4000, "EU4")
116
+
117
+ "$0.04".to_money("BAR").should == Money.new(400, "BAR")
118
+ "€0,04".to_money("EU4").should == Money.new(400, "EU4")
119
+
120
+ "$0.004".to_money("BAR").should == Money.new(40, "BAR")
121
+ "€0,004".to_money("EU4").should == Money.new(40, "EU4")
122
+
123
+ "$0.0004".to_money("BAR").should == Money.new(4, "BAR")
124
+ "€0,0004".to_money("EU4").should == Money.new(4, "EU4")
125
+
126
+ "$0.0024".to_money("BAR").should == Money.new(24, "BAR")
127
+ "€0,0024".to_money("EU4").should == Money.new(24, "EU4")
128
+
129
+ "$0.0324".to_money("BAR").should == Money.new(324, "BAR")
130
+ "€0,0324".to_money("EU4").should == Money.new(324, "EU4")
131
+
132
+ "$0.5324".to_money("BAR").should == Money.new(5324, "BAR")
133
+ "€0,5324".to_money("EU4").should == Money.new(5324, "EU4")
134
+
135
+ "$6.5324".to_money("BAR").should == Money.new(65324, "BAR")
136
+ "€6,5324".to_money("EU4").should == Money.new(65324, "EU4")
137
+
138
+ "$86.5324".to_money("BAR").should == Money.new(865324, "BAR")
139
+ "€86,5324".to_money("EU4").should == Money.new(865324, "EU4")
140
+
141
+ "$186.5324".to_money("BAR").should == Money.new(1865324, "BAR")
142
+ "€186,5324".to_money("EU4").should == Money.new(1865324, "EU4")
143
+
144
+ "$3,331.0034".to_money("BAR").should == Money.new(33310034, "BAR")
145
+ "€3.331,0034".to_money("EU4").should == Money.new(33310034, "EU4")
146
+
147
+ "$8,883,331.0034".to_money("BAR").should == Money.new(88833310034, "BAR")
148
+ "€8.883.331,0034".to_money("EU4").should == Money.new(88833310034, "EU4")
149
+ end
150
+ end
66
151
  end
67
152
 
68
153
  describe ".from_string" do
@@ -207,4 +292,16 @@ describe Money, "parsing" do
207
292
  end
208
293
  end
209
294
 
295
+ describe ".extract_cents" do
296
+ it "correctly treats pipe marks '|' in input (regression test)" do
297
+ Money.extract_cents('100|0').should == Money.extract_cents('100!0')
298
+ end
299
+ end
300
+
301
+ context "given the same inputs to .parse and .from_*" do
302
+ it "gives the same results" do
303
+ 4.635.to_money.should == "4.635".to_money
304
+ end
305
+ end
306
+
210
307
  end
metadata CHANGED
@@ -1,8 +1,8 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: money
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.2
5
- prerelease:
4
+ version: 5.0.0.rc1
5
+ prerelease: 6
6
6
  platform: ruby
7
7
  authors:
8
8
  - Tobias Luetke
@@ -13,11 +13,11 @@ authors:
13
13
  autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
- date: 2012-02-03 00:00:00.000000000Z
16
+ date: 2012-04-02 00:00:00.000000000 Z
17
17
  dependencies:
18
18
  - !ruby/object:Gem::Dependency
19
19
  name: i18n
20
- requirement: &70234938554880 !ruby/object:Gem::Requirement
20
+ requirement: &70315360313800 !ruby/object:Gem::Requirement
21
21
  none: false
22
22
  requirements:
23
23
  - - ~>
@@ -25,10 +25,10 @@ dependencies:
25
25
  version: '0.4'
26
26
  type: :runtime
27
27
  prerelease: false
28
- version_requirements: *70234938554880
28
+ version_requirements: *70315360313800
29
29
  - !ruby/object:Gem::Dependency
30
30
  name: json
31
- requirement: &70234938553840 !ruby/object:Gem::Requirement
31
+ requirement: &70315360313420 !ruby/object:Gem::Requirement
32
32
  none: false
33
33
  requirements:
34
34
  - - ! '>='
@@ -36,10 +36,10 @@ dependencies:
36
36
  version: '0'
37
37
  type: :runtime
38
38
  prerelease: false
39
- version_requirements: *70234938553840
39
+ version_requirements: *70315360313420
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: rspec
42
- requirement: &70234938547740 !ruby/object:Gem::Requirement
42
+ requirement: &70315360312880 !ruby/object:Gem::Requirement
43
43
  none: false
44
44
  requirements:
45
45
  - - ~>
@@ -47,10 +47,10 @@ dependencies:
47
47
  version: 2.8.0
48
48
  type: :development
49
49
  prerelease: false
50
- version_requirements: *70234938547740
50
+ version_requirements: *70315360312880
51
51
  - !ruby/object:Gem::Dependency
52
52
  name: yard
53
- requirement: &70234938546720 !ruby/object:Gem::Requirement
53
+ requirement: &70315360328840 !ruby/object:Gem::Requirement
54
54
  none: false
55
55
  requirements:
56
56
  - - ! '>='
@@ -58,7 +58,7 @@ dependencies:
58
58
  version: '0'
59
59
  type: :development
60
60
  prerelease: false
61
- version_requirements: *70234938546720
61
+ version_requirements: *70315360328840
62
62
  description: This library aids one in handling money and different currencies.
63
63
  email:
64
64
  - semmons99+RubyMoney@gmail.com