money 6.6.1 → 6.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -53,7 +53,7 @@ class Money
53
53
  def currencies_by_name
54
54
  {}.tap do |r|
55
55
  table.each do |dummy,c|
56
- name_parts = c[:name].downcase.split
56
+ name_parts = c[:name].unaccent.downcase.split
57
57
  name_parts.each {|part| part.chomp!('.')}
58
58
 
59
59
  # construct one branch per word
@@ -94,7 +94,7 @@ class Money
94
94
  str.gsub!(/[0-9][\.,:0-9]*[0-9]/,'')
95
95
  str.gsub!(/[0-9]/, '')
96
96
  str.downcase!
97
- @words = str.split
97
+ @words = str.unaccent.split
98
98
  @words.each {|word| word.chomp!('.'); word.chomp!(',') }
99
99
  end
100
100
 
@@ -151,3 +151,4 @@ class Money
151
151
  end
152
152
  end
153
153
  end
154
+
data/lib/money/money.rb CHANGED
@@ -472,9 +472,9 @@ class Money
472
472
  end
473
473
 
474
474
  # Allocates money between different parties without losing pennies.
475
- # After the mathmatically split has been performed, left over pennies will
475
+ # After the mathematical split has been performed, leftover pennies will
476
476
  # be distributed round-robin amongst the parties. This means that parties
477
- # listed first will likely recieve more pennies then ones that are listed later
477
+ # listed first will likely receive more pennies than ones that are listed later
478
478
  #
479
479
  # @param [Array<Numeric>] splits [0.50, 0.25, 0.25] to give 50% of the cash to party1, 25% to party2, and 25% to party3.
480
480
  #
@@ -1,4 +1,17 @@
1
1
  class Money
2
+ CoercedNumber = Struct.new(:value) do
3
+ include Comparable
4
+
5
+ def +(other) raise TypeError; end
6
+ def -(other) raise TypeError; end
7
+ def /(other) raise TypeError; end
8
+ def <=>(other) raise TypeError; end
9
+
10
+ def *(other)
11
+ other * value
12
+ end
13
+ end
14
+
2
15
  module Arithmetic
3
16
 
4
17
  # Returns a money object with changed polarity.
@@ -11,22 +24,23 @@ class Money
11
24
  self.class.new(-fractional, currency)
12
25
  end
13
26
 
14
- # Checks whether two money objects have the same currency and the same
15
- # amount. If money objects have a different currency it will only be true
16
- # if the amounts are both zero. Checks against objects that do not respond
17
- # to #to_money, in which case, it will always return false.
27
+ # Checks whether two Money objects have the same currency and the same
28
+ # amount. If Money objects have a different currency it will only be true
29
+ # if the amounts are both zero. Checks against objects that are not Money or
30
+ # a subclass will always return false.
18
31
  #
19
32
  # @param [Money] other_money Value to compare with.
20
33
  #
21
34
  # @return [Boolean]
22
35
  #
23
36
  # @example
24
- # Money.new(100).eql?(Money.new(101)) #=> false
25
- # Money.new(100).eql?(Money.new(100)) #=> true
26
- # Money.new(0, "USD").eql?(Money.new(0, "EUR")) #=> true
37
+ # Money.new(100).eql?(Money.new(101)) #=> false
38
+ # Money.new(100).eql?(Money.new(100)) #=> true
39
+ # Money.new(100, "USD").eql?(Money.new(100, "GBP")) #=> false
40
+ # Money.new(0, "USD").eql?(Money.new(0, "EUR")) #=> true
41
+ # Money.new(100).eql?("1.00") #=> false
27
42
  def eql?(other_money)
28
- if other_money.respond_to?(:to_money)
29
- other_money = other_money.to_money
43
+ if other_money.is_a?(Money)
30
44
  (fractional == other_money.fractional && currency == other_money.currency) ||
31
45
  (fractional == 0 && other_money.fractional == 0)
32
46
  else
@@ -34,14 +48,23 @@ class Money
34
48
  end
35
49
  end
36
50
 
37
- def <=>(val)
38
- if val.respond_to?(:to_money)
39
- val = val.to_money unless val.respond_to?(:fractional)
40
- if fractional != 0 && val.fractional != 0 && currency != val.currency
41
- val = val.exchange_to(currency)
42
- end
43
- fractional <=> val.fractional
51
+ # Compares two Money objects. If money objects have a different currency it
52
+ # will attempt to convert the currency.
53
+ #
54
+ # @param [Money] other_money Value to compare with.
55
+ #
56
+ # @return [Fixnum]
57
+ #
58
+ # @raise [TypeError] when other object is not Money
59
+ #
60
+ def <=>(other_money)
61
+ return nil unless other_money.is_a?(Money)
62
+ if fractional != 0 && other_money.fractional != 0 && currency != other_money.currency
63
+ other_money = other_money.exchange_to(currency)
44
64
  end
65
+ fractional <=> other_money.fractional
66
+ rescue Money::Bank::UnknownRate
67
+ nil
45
68
  end
46
69
 
47
70
  # Test if the amount is positive. Returns +true+ if the money amount is
@@ -81,7 +104,8 @@ class Money
81
104
  # @example
82
105
  # Money.new(100) + Money.new(100) #=> #<Money @fractional=200>
83
106
  def +(other_money)
84
- return self if other_money == 0
107
+ return self if other_money.zero?
108
+ raise TypeError unless other_money.is_a?(Money)
85
109
  other_money = other_money.exchange_to(currency)
86
110
  self.class.new(fractional + other_money.fractional, currency)
87
111
  end
@@ -98,7 +122,8 @@ class Money
98
122
  # @example
99
123
  # Money.new(100) - Money.new(99) #=> #<Money @fractional=1>
100
124
  def -(other_money)
101
- return self if other_money == 0
125
+ return self if other_money.zero?
126
+ raise TypeError unless other_money.is_a?(Money)
102
127
  other_money = other_money.exchange_to(currency)
103
128
  self.class.new(fractional - other_money.fractional, currency)
104
129
  end
@@ -279,7 +304,7 @@ class Money
279
304
  # @example
280
305
  # 2 * Money.new(10) #=> #<Money @fractional=20>
281
306
  def coerce(other)
282
- [self, other]
307
+ [CoercedNumber.new(other), self]
283
308
  end
284
309
  end
285
310
  end
@@ -1,30 +1,6 @@
1
1
  # encoding: UTF-8
2
2
  class Money
3
3
  module Formatting
4
- def self.included(base)
5
- [
6
- [:thousands_separator, :delimiter, ","],
7
- [:decimal_mark, :separator, "."]
8
- ].each do |method, name, character|
9
- define_i18n_method(method, name, character)
10
- end
11
- end
12
-
13
- def self.define_i18n_method(method, name, character)
14
- define_method(method) do
15
- if self.class.use_i18n
16
- begin
17
- I18n.t name, :scope => "number.currency.format", :raise => true
18
- rescue I18n::MissingTranslationData
19
- I18n.t name, :scope =>"number.format", :default => (currency.send(method) || character)
20
- end
21
- else
22
- currency.send(method) || character
23
- end
24
- end
25
- alias_method name, method
26
- end
27
-
28
4
  # Creates a formatted price string according to several rules.
29
5
  #
30
6
  # @param [Hash] rules The options used to format the string.
@@ -215,6 +191,10 @@ class Money
215
191
  # # CAD: "CAD$"
216
192
  # Money.new(10000, "CAD").format(:translate => true) #=> "CAD$100.00"
217
193
  #
194
+ # @example
195
+ # Money.new(89000, :btc).format(:drop_trailing_zeros => true) #=> B⃦0.00089
196
+ # Money.new(110, :usd).format(:drop_trailing_zeros => true) #=> $1.1
197
+ #
218
198
  # Note that the default rules can be defined through +Money.default_formatting_rules+ hash.
219
199
  #
220
200
  # @see +Money.default_formatting_rules+ for more information.
@@ -239,13 +219,13 @@ class Money
239
219
  formatted = self.abs.to_s
240
220
 
241
221
  if rules[:rounded_infinite_precision]
242
- formatted.gsub!(/#{currency.decimal_mark}/, '.') unless '.' == currency.decimal_mark
222
+ formatted.gsub!(/#{decimal_mark}/, '.') unless '.' == decimal_mark
243
223
  formatted = ((BigDecimal(formatted) * currency.subunit_to_unit).round / BigDecimal(currency.subunit_to_unit.to_s)).to_s("F")
244
224
  formatted.gsub!(/\..*/) do |decimal_part|
245
225
  decimal_part << '0' while decimal_part.length < (currency.decimal_places + 1)
246
226
  decimal_part
247
227
  end
248
- formatted.gsub!(/\./, currency.decimal_mark) unless '.' == currency.decimal_mark
228
+ formatted.gsub!(/\./, decimal_mark) unless '.' == decimal_mark
249
229
  end
250
230
 
251
231
  sign = self.negative? ? '-' : ''
@@ -254,6 +234,12 @@ class Money
254
234
  formatted = "#{formatted.to_i}"
255
235
  end
256
236
 
237
+ # Inspiration: https://github.com/rails/rails/blob/16214d1108c31174c94503caced3855b0f6bad95/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb#L72-L79
238
+ if rules[:drop_trailing_zeros]
239
+ escaped_decimal_mark = Regexp.escape(decimal_mark)
240
+ formatted = formatted.sub(/(#{escaped_decimal_mark})(\d*[1-9])?0+\z/, '\1\2').sub(/#{escaped_decimal_mark}\z/, '')
241
+ end
242
+
257
243
  thousands_separator_value = thousands_separator
258
244
  # Determine thousands_separator
259
245
  if rules.has_key?(:thousands_separator)
@@ -300,8 +286,31 @@ class Money
300
286
  formatted
301
287
  end
302
288
 
289
+ def thousands_separator
290
+ i18n_format_for(:thousands_separator, :delimiter, ",")
291
+ end
292
+
293
+ def decimal_mark
294
+ i18n_format_for(:decimal_mark, :separator, ".")
295
+ end
296
+
297
+ alias_method :delimiter, :thousands_separator
298
+ alias_method :separator, :decimal_mark
299
+
303
300
  private
304
301
 
302
+ def i18n_format_for(method, name, character)
303
+ if self.class.use_i18n
304
+ begin
305
+ I18n.t name, :scope => "number.currency.format", :raise => true
306
+ rescue I18n::MissingTranslationData
307
+ I18n.t name, :scope =>"number.format", :default => (currency.send(method) || character)
308
+ end
309
+ else
310
+ currency.send(method) || character
311
+ end
312
+ end
313
+
305
314
  # Cleans up formatting rules.
306
315
  #
307
316
  # @param [Hash] rules
data/lib/money/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  class Money
2
- VERSION = "6.6.1"
2
+ VERSION = "6.7.0"
3
3
  end
data/money.gemspec CHANGED
@@ -26,10 +26,11 @@ Test responsibly :-)
26
26
  MSG
27
27
 
28
28
  s.add_dependency 'i18n', ['>= 0.6.4', '<= 0.7.0']
29
+ s.add_dependency 'sixarm_ruby_unaccent', ['>= 1.1.1', '< 2']
29
30
 
30
31
  s.add_development_dependency "bundler", "~> 1.3"
31
32
  s.add_development_dependency "rake"
32
- s.add_development_dependency "rspec", "~> 3.2.0"
33
+ s.add_development_dependency "rspec", "~> 3.4.0"
33
34
  s.add_development_dependency "yard", "~> 0.8"
34
35
  s.add_development_dependency "kramdown", "~> 1.1"
35
36
 
@@ -1,5 +1,3 @@
1
- require 'spec_helper'
2
-
3
1
  class Money
4
2
  module Bank
5
3
  describe Base do
@@ -1,5 +1,3 @@
1
- require 'spec_helper'
2
-
3
1
  class Money
4
2
  module Bank
5
3
  describe SingleCurrency do
@@ -1,4 +1,3 @@
1
- require 'spec_helper'
2
1
  require 'json'
3
2
  require 'yaml'
4
3
 
@@ -1,5 +1,4 @@
1
1
  # encoding: utf-8
2
- require 'spec_helper'
3
2
 
4
3
  describe Money::Currency::Heuristics do
5
4
  describe "#analyze_string" do
@@ -77,6 +76,85 @@ describe Money::Currency::Heuristics do
77
76
  expect(it.analyze("10EUR is less than 100:- but really, I want US$1")).to eq ['EUR', 'SEK', 'USD']
78
77
  end
79
78
 
79
+ it "find currencies with normal characters in name using unaccent" do
80
+ expect(it.analyze("10 Nicaraguan Cordoba")).to eq ["NIO"]
81
+ end
82
+
83
+ it "find currencies with special characters in name using unaccent" do
84
+ expect(it.analyze("10 Nicaraguan Córdoba")).to eq ["NIO"]
85
+ end
86
+
87
+ it "find currencies with special symbols using unaccent" do
88
+ expect(it.analyze("ل.س")).not_to eq []
89
+ expect(it.analyze("R₣")).not_to eq []
90
+ expect(it.analyze("ரூ")).not_to eq []
91
+ expect(it.analyze("රු")).not_to eq []
92
+ expect(it.analyze("сом")).not_to eq []
93
+ expect(it.analyze("圓")).not_to eq []
94
+ expect(it.analyze("円")).not_to eq []
95
+ expect(it.analyze("৳")).not_to eq []
96
+ expect(it.analyze("૱")).not_to eq []
97
+ expect(it.analyze("௹")).not_to eq []
98
+ expect(it.analyze("रु")).not_to eq []
99
+ expect(it.analyze("ש״ח")).not_to eq []
100
+ expect(it.analyze("元")).not_to eq []
101
+ expect(it.analyze("¢")).not_to eq []
102
+ expect(it.analyze("£")).not_to eq []
103
+ expect(it.analyze("€")).not_to eq []
104
+ expect(it.analyze("¥")).not_to eq []
105
+ expect(it.analyze("د.إ")).not_to eq []
106
+ expect(it.analyze("؋")).not_to eq []
107
+ expect(it.analyze("դր.")).not_to eq []
108
+ expect(it.analyze("ƒ")).not_to eq []
109
+ expect(it.analyze("₼")).not_to eq []
110
+ expect(it.analyze("৳")).not_to eq []
111
+ expect(it.analyze("лв")).not_to eq []
112
+ expect(it.analyze("лев")).not_to eq []
113
+ expect(it.analyze("дин")).not_to eq []
114
+ expect(it.analyze("ب.د")).not_to eq []
115
+ expect(it.analyze("₡")).not_to eq []
116
+ expect(it.analyze("Kč")).not_to eq []
117
+ expect(it.analyze("د.ج")).not_to eq []
118
+ expect(it.analyze("ج.م")).not_to eq []
119
+ expect(it.analyze("ლ")).not_to eq []
120
+ expect(it.analyze("₵")).not_to eq []
121
+ expect(it.analyze("₪")).not_to eq []
122
+ expect(it.analyze("₹")).not_to eq []
123
+ expect(it.analyze("ع.د")).not_to eq []
124
+ expect(it.analyze("﷼")).not_to eq []
125
+ expect(it.analyze("د.ا")).not_to eq []
126
+ expect(it.analyze("៛")).not_to eq []
127
+ expect(it.analyze("₩")).not_to eq []
128
+ expect(it.analyze("د.ك")).not_to eq []
129
+ expect(it.analyze("〒")).not_to eq []
130
+ expect(it.analyze("₭")).not_to eq []
131
+ expect(it.analyze("ل.ل")).not_to eq []
132
+ expect(it.analyze("₨")).not_to eq []
133
+ expect(it.analyze("ل.د")).not_to eq []
134
+ expect(it.analyze("د.م.")).not_to eq []
135
+ expect(it.analyze("ден")).not_to eq []
136
+ expect(it.analyze("₮")).not_to eq []
137
+ expect(it.analyze("₦")).not_to eq []
138
+ expect(it.analyze("C$")).not_to eq []
139
+ expect(it.analyze("ر.ع.")).not_to eq []
140
+ expect(it.analyze("₱")).not_to eq []
141
+ expect(it.analyze("zł")).not_to eq []
142
+ expect(it.analyze("₲")).not_to eq []
143
+ expect(it.analyze("ر.ق")).not_to eq []
144
+ expect(it.analyze("РСД")).not_to eq []
145
+ expect(it.analyze("₽")).not_to eq []
146
+ expect(it.analyze("ر.س")).not_to eq []
147
+ expect(it.analyze("฿")).not_to eq []
148
+ expect(it.analyze("د.ت")).not_to eq []
149
+ expect(it.analyze("T$")).not_to eq []
150
+ expect(it.analyze("₺")).not_to eq []
151
+ expect(it.analyze("₴")).not_to eq []
152
+ expect(it.analyze("₫")).not_to eq []
153
+ expect(it.analyze("B⃦")).not_to eq []
154
+ expect(it.analyze("₤")).not_to eq []
155
+ expect(it.analyze("ރ")).not_to eq []
156
+ end
157
+
80
158
  it "should function with unicode characters" do
81
159
  expect(it.analyze("10 դր.")).to eq ["AMD"]
82
160
  end
@@ -1,5 +1,4 @@
1
1
  # encoding: utf-8
2
- require 'spec_helper'
3
2
 
4
3
  describe Money::Currency::Loader do
5
4
  class CurrencyLoader
@@ -1,11 +1,9 @@
1
1
  # encoding: utf-8
2
2
 
3
- require "spec_helper"
4
-
5
3
  class Money
6
4
  describe Currency do
7
5
 
8
- 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": ",", "smallest_denomination": 1 }'
6
+ FOO = '{ "priority": 1, "iso_code": "FOO", "iso_numeric": "840", "name": "United States Dollar", "symbol": "$", "subunit": "Cent", "subunit_to_unit": 1000, "symbol_first": true, "html_entity": "$", "decimal_mark": ".", "thousands_separator": ",", "smallest_denomination": 1 }'
9
7
 
10
8
  def register_foo(opts={})
11
9
  foo_attrs = JSON.parse(FOO, :symbolize_names => true)
@@ -339,21 +337,21 @@ class Money
339
337
 
340
338
  describe "#exponent" do
341
339
  it "conforms to iso 4217" do
342
- Currency.new(:jpy).exponent == 0
343
- Currency.new(:usd).exponent == 2
344
- Currency.new(:iqd).exponent == 3
340
+ expect(Currency.new(:jpy).exponent).to eq 0
341
+ expect(Currency.new(:usd).exponent).to eq 2
342
+ expect(Currency.new(:iqd).exponent).to eq 3
345
343
  end
346
344
  end
347
345
 
348
346
  describe "#decimal_places" do
349
347
  it "proper places for known currency" do
350
- Currency.new(:mro).decimal_places == 1
351
- Currency.new(:usd).decimal_places == 2
348
+ expect(Currency.new(:mro).decimal_places).to eq 1
349
+ expect(Currency.new(:usd).decimal_places).to eq 2
352
350
  end
353
351
 
354
352
  it "proper places for custom currency" do
355
353
  register_foo
356
- Currency.new(:foo).decimal_places == 3
354
+ expect(Currency.new(:foo).decimal_places).to eq 3
357
355
  unregister_foo
358
356
  end
359
357
  end
@@ -1,7 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
- require "spec_helper"
4
-
5
3
  describe Money do
6
4
  describe "-@" do
7
5
  it "changes the sign of a number" do
@@ -31,29 +29,21 @@ describe Money do
31
29
  expect(Money.new(0, "USD")).to eq Money.new(0, "JPY")
32
30
  end
33
31
 
34
- it "returns false if used to compare with an object that doesn't respond to #to_money" do
32
+ it "returns false if used to compare with an object that doesn't inherit from Money" do
35
33
  expect(Money.new(1_00, "USD")).not_to eq Object.new
36
34
  expect(Money.new(1_00, "USD")).not_to eq Class
37
35
  expect(Money.new(1_00, "USD")).not_to eq Kernel
38
- expect(Money.new(1_00, "USD")).not_to eq /foo/
36
+ expect(Money.new(1_00, "USD")).not_to eq(/foo/)
39
37
  expect(Money.new(1_00, "USD")).not_to eq nil
40
38
  end
41
39
 
42
- it "can be used to compare with an object that responds to #to_money" do
43
- klass = Class.new do
44
- def initialize(money)
45
- @money = money
46
- end
47
-
48
- def to_money
49
- @money
50
- end
51
- end
40
+ it "can be used to compare with an object that inherits from Money" do
41
+ klass = Class.new(Money)
52
42
 
53
- expect(Money.new(1_00, "USD")).to eq klass.new(Money.new(1_00, "USD"))
54
- expect(Money.new(2_50, "USD")).to eq klass.new(Money.new(2_50, "USD"))
55
- expect(Money.new(2_50, "USD")).not_to eq klass.new(Money.new(3_00, "USD"))
56
- expect(Money.new(1_00, "GBP")).not_to eq klass.new(Money.new(1_00, "USD"))
43
+ expect(Money.new(1_00, "USD")).to eq klass.new(1_00, "USD")
44
+ expect(Money.new(2_50, "USD")).to eq klass.new(2_50, "USD")
45
+ expect(Money.new(2_50, "USD")).not_to eq klass.new(3_00, "USD")
46
+ expect(Money.new(1_00, "GBP")).not_to eq klass.new(1_00, "USD")
57
47
  end
58
48
  end
59
49
 
@@ -65,7 +55,7 @@ describe Money do
65
55
  expect(Money.new(1_00, "USD").eql?(Money.new(99_00, "EUR"))).to be false
66
56
  end
67
57
 
68
- it "returns false if used to compare with an object that doesn't respond to #to_money" do
58
+ it "returns false if used to compare with an object that doesn't inherit from Money" do
69
59
  expect(Money.new(1_00, "USD").eql?(Object.new)).to be false
70
60
  expect(Money.new(1_00, "USD").eql?(Class)).to be false
71
61
  expect(Money.new(1_00, "USD").eql?(Kernel)).to be false
@@ -73,21 +63,13 @@ describe Money do
73
63
  expect(Money.new(1_00, "USD").eql?(nil)).to be false
74
64
  end
75
65
 
76
- it "can be used to compare with an object that responds to #to_money" do
77
- klass = Class.new do
78
- def initialize(money)
79
- @money = money
80
- end
66
+ it "can be used to compare with an object that inherits from Money" do
67
+ klass = Class.new(Money)
81
68
 
82
- def to_money
83
- @money
84
- end
85
- end
86
-
87
- expect(Money.new(1_00, "USD").eql?(klass.new(Money.new(1_00, "USD")))).to be true
88
- expect(Money.new(2_50, "USD").eql?(klass.new(Money.new(2_50, "USD")))).to be true
89
- expect(Money.new(2_50, "USD").eql?(klass.new(Money.new(3_00, "USD")))).to be false
90
- expect(Money.new(1_00, "GBP").eql?(klass.new(Money.new(1_00, "USD")))).to be false
69
+ expect(Money.new(1_00, "USD").eql?(klass.new(1_00, "USD"))).to be true
70
+ expect(Money.new(2_50, "USD").eql?(klass.new(2_50, "USD"))).to be true
71
+ expect(Money.new(2_50, "USD").eql?(klass.new(3_00, "USD"))).to be false
72
+ expect(Money.new(1_00, "GBP").eql?(klass.new(1_00, "USD"))).to be false
91
73
  end
92
74
  end
93
75
 
@@ -112,26 +94,29 @@ describe Money do
112
94
  expect(Money.new(100_00, "USD") <=> target).to be > 0
113
95
  end
114
96
 
115
- it "can be used to compare with an object that responds to #to_money" do
116
- klass = Class.new do
117
- def initialize(money)
118
- @money = money
119
- end
97
+ it "returns nil if currency conversion fails, and therefore cannot be compared" do
98
+ target = Money.new(200_00, "EUR")
99
+ expect(target).to receive(:exchange_to).with(Money::Currency.new("USD")).and_raise(Money::Bank::UnknownRate)
100
+ expect(Money.new(100_00, "USD") <=> target).to be_nil
101
+ end
120
102
 
121
- def to_money
122
- @money
123
- end
124
- end
103
+ it "can be used to compare with an object that inherits from Money" do
104
+ klass = Class.new(Money)
125
105
 
126
- expect(Money.new(1_00) <=> klass.new(Money.new(1_00))).to eq 0
127
- expect(Money.new(1_00) <=> klass.new(Money.new(99))).to be > 0
128
- expect(Money.new(1_00) <=> klass.new(Money.new(2_00))).to be < 0
106
+ expect(Money.new(1_00) <=> klass.new(1_00)).to eq 0
107
+ expect(Money.new(1_00) <=> klass.new(99)).to be > 0
108
+ expect(Money.new(1_00) <=> klass.new(2_00)).to be < 0
129
109
  end
130
110
 
131
- it "returns nil when used to compare with an object that doesn't respond to #to_money" do
111
+ it "raises TypeError when used to compare with an object that doesn't inherit from Money" do
112
+ expect(Money.new(1_00) <=> 100).to be_nil
113
+
132
114
  expect(Money.new(1_00) <=> Object.new).to be_nil
115
+
133
116
  expect(Money.new(1_00) <=> Class).to be_nil
117
+
134
118
  expect(Money.new(1_00) <=> Kernel).to be_nil
119
+
135
120
  expect(Money.new(1_00) <=> /foo/).to be_nil
136
121
  end
137
122
  end
@@ -586,5 +571,73 @@ describe Money do
586
571
  result = 2 * Money.new(4, 'USD')
587
572
  expect(result).to eq Money.new(8, 'USD')
588
573
  end
574
+
575
+ it "raises TypeError dividing by a Money (unless other is a Money)" do
576
+ expect {
577
+ 2 / Money.new(2, 'USD')
578
+ }.to raise_exception(TypeError)
579
+ end
580
+
581
+ it "raises TypeError subtracting by a Money (unless other is a Money)" do
582
+ expect {
583
+ 2 - Money.new(2, 'USD')
584
+ }.to raise_exception(TypeError)
585
+ end
586
+
587
+ it "raises TypeError adding by a Money (unless other is a Money)" do
588
+ expect {
589
+ 2 + Money.new(2, 'USD')
590
+ }.to raise_exception(TypeError)
591
+ end
592
+
593
+ it "treats multiplication as commutative" do
594
+ expect {
595
+ 2 * Money.new(2, 'USD')
596
+ }.to_not raise_exception
597
+ result = 2 * Money.new(2, 'USD')
598
+ expect(result).to eq(Money.new(4, 'USD'))
599
+ end
600
+
601
+ it "doesn't work with non-numerics" do
602
+ expect {
603
+ "2" * Money.new(2, 'USD')
604
+ }.to raise_exception(TypeError)
605
+ end
606
+
607
+ it "correctly handles <=>" do
608
+ expect {
609
+ 2 < Money.new(2, 'USD')
610
+ }.to raise_exception(TypeError)
611
+
612
+ expect {
613
+ 2 > Money.new(2, 'USD')
614
+ }.to raise_exception(TypeError)
615
+
616
+ expect {
617
+ 2 <= Money.new(2, 'USD')
618
+ }.to raise_exception(TypeError)
619
+
620
+ expect {
621
+ 2 >= Money.new(2, 'USD')
622
+ }.to raise_exception(TypeError)
623
+
624
+ expect {
625
+ 2 <=> Money.new(2, 'USD')
626
+ }.to raise_exception(TypeError)
627
+ end
628
+
629
+ it "raises exceptions for all numeric types, not just Integer" do
630
+ expect {
631
+ 2.0 / Money.new(2, 'USD')
632
+ }.to raise_exception(TypeError)
633
+
634
+ expect {
635
+ Rational(2,3) / Money.new(2, 'USD')
636
+ }.to raise_exception(TypeError)
637
+
638
+ expect {
639
+ BigDecimal(2) / Money.new(2, 'USD')
640
+ }.to raise_exception(TypeError)
641
+ end
589
642
  end
590
643
  end