money 5.0.0 → 5.1.0.beta1

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/money.gemspec CHANGED
@@ -1,7 +1,7 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
  Gem::Specification.new do |s|
3
3
  s.name = "money"
4
- s.version = "5.0.0"
4
+ s.version = "5.1.0.beta1"
5
5
  s.platform = Gem::Platform::RUBY
6
6
  s.authors = ["Tobias Luetke", "Hongli Lai", "Jeremy McNevin",
7
7
  "Shane Emmons", "Simone Carletti"]
@@ -10,20 +10,18 @@ Gem::Specification.new do |s|
10
10
  s.summary = "Money and currency exchange support library."
11
11
  s.description = "This library aids one in handling money and different currencies."
12
12
 
13
+ s.required_ruby_version = ">= 1.9.2"
13
14
  s.required_rubygems_version = ">= 1.3.6"
14
15
 
15
- s.add_dependency "i18n", "~> 0.4"
16
- s.add_dependency "json"
16
+ s.add_dependency "i18n", "~> 0.6.0"
17
17
 
18
- s.add_development_dependency "rspec", "~> 2.9.0"
19
- s.add_development_dependency "yard", "~> 0.7.5"
20
- s.add_development_dependency "redcarpet", "~> 2.1.1"
21
-
22
- s.requirements << "json"
18
+ s.add_development_dependency "rspec", "~> 2.10.0"
19
+ s.add_development_dependency "yard", "~> 0.8.1"
20
+ s.add_development_dependency "kramdown", "~> 0.13.7"
23
21
 
24
22
  s.files = Dir.glob("{config,lib,spec}/**/*")
25
23
  s.files += %w(CHANGELOG.md LICENSE README.md)
26
- s.files += %w(Rakefile .gemtest money.gemspec)
24
+ s.files += %w(Rakefile money.gemspec)
27
25
 
28
26
  s.require_path = "lib"
29
27
  end
@@ -2,6 +2,14 @@ require 'spec_helper'
2
2
 
3
3
  describe Money::Bank::Base do
4
4
 
5
+ describe ".instance" do
6
+ it "is local to one class" do
7
+ klass = Money::Bank::Base
8
+ subclass = Class.new(Money::Bank::Base)
9
+ klass.instance.should_not == subclass.instance
10
+ end
11
+ end
12
+
5
13
  describe "#initialize" do
6
14
  it "accepts a block and stores @rounding_method" do
7
15
  proc = Proc.new { |n| n.ceil }
@@ -147,7 +147,7 @@ describe Money::Bank::VariableExchange do
147
147
  it "writes rates to file" do
148
148
  f = mock('IO')
149
149
  File.should_receive(:open).with('null', 'w').and_yield(f)
150
- f.should_receive(:write).with(@rates.to_json)
150
+ f.should_receive(:write).with(JSON.dump(@rates))
151
151
 
152
152
  subject.export_rates(:json, 'null')
153
153
  end
@@ -109,9 +109,8 @@ describe Money::Currency do
109
109
  end
110
110
 
111
111
  it "doesn't create new symbols indefinitely" do
112
- expect {
113
- expect { Money::Currency.new("bogus") }.to raise_exception(Money::Currency::UnknownCurrency)
114
- }.to_not change{ Symbol.all_symbols.size }
112
+ expect { Money::Currency.new("bogus") }.to raise_exception(Money::Currency::UnknownCurrency)
113
+ Symbol.all_symbols.map{|s| s.to_s}.should_not include("bogus")
115
114
  end
116
115
  end
117
116
 
@@ -3,7 +3,6 @@
3
3
  require "spec_helper"
4
4
 
5
5
  describe Money do
6
-
7
6
  describe "-@" do
8
7
  it "changes the sign of a number" do
9
8
  (- Money.new(0)).should == Money.new(0)
@@ -275,6 +274,28 @@ describe Money do
275
274
  (t[:a] / t[:b]).should == t[:c]
276
275
  end
277
276
  end
277
+
278
+ context "infinite_precision = true" do
279
+ before do
280
+ Money.infinite_precision = true
281
+ end
282
+
283
+ after do
284
+ Money.infinite_precision = false
285
+ end
286
+
287
+ it "uses BigDecimal division" do
288
+ ts = [
289
+ {:a => Money.new( 13, :USD), :b => 4, :c => Money.new( 3.25, :USD)},
290
+ {:a => Money.new( 13, :USD), :b => -4, :c => Money.new(-3.25, :USD)},
291
+ {:a => Money.new(-13, :USD), :b => 4, :c => Money.new(-3.25, :USD)},
292
+ {:a => Money.new(-13, :USD), :b => -4, :c => Money.new( 3.25, :USD)},
293
+ ]
294
+ ts.each do |t|
295
+ (t[:a] / t[:b]).should == t[:c]
296
+ end
297
+ end
298
+ end
278
299
  end
279
300
 
280
301
  describe "#div" do
@@ -314,6 +335,28 @@ describe Money do
314
335
  t[:a].div(t[:b]).should == t[:c]
315
336
  end
316
337
  end
338
+
339
+ context "infinite_precision = true" do
340
+ before do
341
+ Money.infinite_precision = true
342
+ end
343
+
344
+ after do
345
+ Money.infinite_precision = false
346
+ end
347
+
348
+ it "uses BigDecimal division" do
349
+ ts = [
350
+ {:a => Money.new( 13, :USD), :b => 4, :c => Money.new( 3.25, :USD)},
351
+ {:a => Money.new( 13, :USD), :b => -4, :c => Money.new(-3.25, :USD)},
352
+ {:a => Money.new(-13, :USD), :b => 4, :c => Money.new(-3.25, :USD)},
353
+ {:a => Money.new(-13, :USD), :b => -4, :c => Money.new( 3.25, :USD)},
354
+ ]
355
+ ts.each do |t|
356
+ t[:a].div(t[:b]).should == t[:c]
357
+ end
358
+ end
359
+ end
317
360
  end
318
361
 
319
362
  describe "#divmod" do
@@ -353,6 +396,28 @@ describe Money do
353
396
  t[:a].divmod(t[:b]).should == t[:c]
354
397
  end
355
398
  end
399
+
400
+ context "infinite_precision = true" do
401
+ before do
402
+ Money.infinite_precision = true
403
+ end
404
+
405
+ after do
406
+ Money.infinite_precision = false
407
+ end
408
+
409
+ it "uses BigDecimal division" do
410
+ ts = [
411
+ {:a => Money.new( 13, :USD), :b => 4, :c => [Money.new( 3, :USD), Money.new( 1, :USD)]},
412
+ {:a => Money.new( 13, :USD), :b => -4, :c => [Money.new(-4, :USD), Money.new(-3, :USD)]},
413
+ {:a => Money.new(-13, :USD), :b => 4, :c => [Money.new(-4, :USD), Money.new( 3, :USD)]},
414
+ {:a => Money.new(-13, :USD), :b => -4, :c => [Money.new( 3, :USD), Money.new(-1, :USD)]},
415
+ ]
416
+ ts.each do |t|
417
+ t[:a].divmod(t[:b]).should == t[:c]
418
+ end
419
+ end
420
+ end
356
421
  end
357
422
 
358
423
  describe "#modulo" do
@@ -506,5 +571,4 @@ describe Money do
506
571
  money.nonzero?.should be_equal(money)
507
572
  end
508
573
  end
509
-
510
574
  end
@@ -5,6 +5,7 @@ require "spec_helper"
5
5
  describe Money, "formatting" do
6
6
 
7
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
+ INDIAN_BAR = '{ "priority": 1, "iso_code": "INDIAN_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": ",", "south_asian_number_formatting": true}'
8
9
  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
10
 
10
11
  context "without i18n" do
@@ -132,7 +133,7 @@ describe Money, "formatting" do
132
133
  one_thousand["BRL"].should == "R$ 1.000,00"
133
134
 
134
135
  # Other
135
- one_thousand["SEK"].should == "kr1,000.00"
136
+ one_thousand["SEK"].should == "1.000,00 kr"
136
137
  one_thousand["GHC"].should == "₵1,000.00"
137
138
  end
138
139
 
@@ -231,7 +232,7 @@ describe Money, "formatting" do
231
232
  one["BRL"].should == "R$ 1,00"
232
233
 
233
234
  # Other
234
- one["SEK"].should == "kr1.00"
235
+ one["SEK"].should == "1,00 kr"
235
236
  one["GHC"].should == "₵1.00"
236
237
  end
237
238
 
@@ -244,7 +245,7 @@ describe Money, "formatting" do
244
245
  specify "(:symbol => some non-Boolean value that evaluates to true) returns symbol based on the given currency code" do
245
246
  Money.new(100, "GBP").format(:symbol => true).should == "£1.00"
246
247
  Money.new(100, "EUR").format(:symbol => true).should == "€1,00"
247
- Money.new(100, "SEK").format(:symbol => true).should == "kr1.00"
248
+ Money.new(100, "SEK").format(:symbol => true).should == "1,00 kr"
248
249
  end
249
250
 
250
251
  specify "(:symbol => "", nil or false) returns the amount without a symbol" do
@@ -282,6 +283,18 @@ describe Money, "formatting" do
282
283
  end
283
284
  end
284
285
 
286
+ describe ":south_asian_number_formatting delimiter" do
287
+ before(:each) do
288
+ Money::Currency.register(JSON.parse(INDIAN_BAR, :symbolize_names => true))
289
+ end
290
+
291
+ specify "(:south_asian_number_formatting => true) works as documented" do
292
+ Money.new(10000000, 'INR').format(:south_asian_number_formatting => true, :symbol => false).should == "1,00,000.00"
293
+ Money.new(1000000000, 'INDIAN_BAR').format(:south_asian_number_formatting => true, :symbol => false).should == "1,00,000.0000"
294
+ Money.new(10000000).format(:south_asian_number_formatting => true).should == "$1,00,000.00"
295
+ end
296
+ end
297
+
285
298
  describe ":thousands_separator option" do
286
299
  specify "(:thousands_separator => a thousands_separator string) works as documented" do
287
300
  Money.us_dollar(100000).format(:thousands_separator => ".").should == "$1.000.00"
@@ -384,10 +397,10 @@ describe Money, "formatting" do
384
397
  end
385
398
 
386
399
  it "brute forces :subunit_to_unit = 100" do
387
- ("0.00".."9.99").each do |amt|
400
+ %w{0.00 0.01 1.00 1.01 5.67 9.99}.each do |amt|
388
401
  amt.to_money("USD").format(:symbol => false).should == amt
389
402
  end
390
- ("-0.01".."-9.99").each do |amt|
403
+ %w{-0.01 -1.00 -1.01 -5.67 -9.99}.each do |amt|
391
404
  amt.to_money("USD").format(:symbol => false).should == amt
392
405
  end
393
406
  "1000.00".to_money("USD").format(:symbol => false).should == "1,000.00"
@@ -395,12 +408,14 @@ describe Money, "formatting" do
395
408
  end
396
409
 
397
410
  it "brute forces :subunit_to_unit = 1000" do
398
- ("0.000".."9.999").each do |amt|
411
+ %w{0.000 0.001 1.000 1.001 5.678 9.999}.each do |amt|
399
412
  amt.to_money("IQD").format(:symbol => false).should == amt
400
413
  end
401
- ("-0.001".."-9.999").each do |amt|
414
+
415
+ %w{-0.001 -1.000 -1.001 -5.678 -9.999}.each do |amt|
402
416
  amt.to_money("IQD").format(:symbol => false).should == amt
403
417
  end
418
+
404
419
  "1000.000".to_money("IQD").format(:symbol => false).should == "1,000.000"
405
420
  "-1000.000".to_money("IQD").format(:symbol => false).should == "-1,000.000"
406
421
  end
data/spec/money_spec.rb CHANGED
@@ -3,7 +3,6 @@
3
3
  require "spec_helper"
4
4
 
5
5
  describe Money do
6
-
7
6
  describe ".new" do
8
7
  it "rounds the given cents to an integer" do
9
8
  Money.new(1.00, "USD").cents.should == 1
@@ -14,48 +13,83 @@ describe Money do
14
13
  it "is associated to the singleton instance of Bank::VariableExchange by default" do
15
14
  Money.new(0).bank.should be(Money::Bank::VariableExchange.instance)
16
15
  end
16
+
17
+ it "handles Rationals" do
18
+ n = Rational(1)
19
+ Money.new(n).cents.should == 1
20
+ end
21
+
22
+ it "handles Floats" do
23
+ n = Float("1")
24
+ Money.new(n).cents.should == 1
25
+ end
26
+
27
+ context "infinite_precision = true" do
28
+ before do
29
+ Money.infinite_precision = true
30
+ end
31
+
32
+ after do
33
+ Money.infinite_precision = false
34
+ end
35
+
36
+ it "doesn't round cents" do
37
+ Money.new(1.01, "USD").cents.should == BigDecimal("1.01")
38
+ Money.new(1.50, "USD").cents.should == BigDecimal("1.50")
39
+ end
40
+ end
17
41
  end
18
42
 
19
43
  describe ".new_with_dollars" do
44
+ it "is synonym of #new_with_amount" do
45
+ MoneyExpectation = Class.new(Money)
46
+ def MoneyExpectation.new_with_amount *args
47
+ args
48
+ end
49
+ MoneyExpectation.new_with_dollars("expectation").should == ["expectation"]
50
+ end
51
+ end
52
+
53
+ describe ".new_with_amount" do
20
54
  it "converts given amount to cents" do
21
- Money.new_with_dollars(1).should == Money.new(100)
22
- Money.new_with_dollars(1, "USD").should == Money.new(100, "USD")
23
- Money.new_with_dollars(1, "EUR").should == Money.new(100, "EUR")
55
+ Money.new_with_amount(1).should == Money.new(100)
56
+ Money.new_with_amount(1, "USD").should == Money.new(100, "USD")
57
+ Money.new_with_amount(1, "EUR").should == Money.new(100, "EUR")
24
58
  end
25
59
 
26
60
  it "respects :subunit_to_unit currency property" do
27
- Money.new_with_dollars(1, "USD").should == Money.new(1_00, "USD")
28
- Money.new_with_dollars(1, "TND").should == Money.new(1_000, "TND")
29
- Money.new_with_dollars(1, "CLP").should == Money.new(1, "CLP")
61
+ Money.new_with_amount(1, "USD").should == Money.new(1_00, "USD")
62
+ Money.new_with_amount(1, "TND").should == Money.new(1_000, "TND")
63
+ Money.new_with_amount(1, "CLP").should == Money.new(1, "CLP")
30
64
  end
31
65
 
32
66
  it "does not loose precision" do
33
- Money.new_with_dollars(1234).cents.should == 1234_00
34
- Money.new_with_dollars(100.37).cents.should == 100_37
35
- Money.new_with_dollars(BigDecimal.new('1234')).cents.should == 1234_00
67
+ Money.new_with_amount(1234).cents.should == 1234_00
68
+ Money.new_with_amount(100.37).cents.should == 100_37
69
+ Money.new_with_amount(BigDecimal.new('1234')).cents.should == 1234_00
36
70
  end
37
71
 
38
72
  it "accepts optional currency" do
39
- m = Money.new_with_dollars(1)
73
+ m = Money.new_with_amount(1)
40
74
  m.currency.should == Money.default_currency
41
75
 
42
- m = Money.new_with_dollars(1, Money::Currency.wrap("EUR"))
76
+ m = Money.new_with_amount(1, Money::Currency.wrap("EUR"))
43
77
  m.currency.should == Money::Currency.wrap("EUR")
44
78
 
45
- m = Money.new_with_dollars(1, "EUR")
79
+ m = Money.new_with_amount(1, "EUR")
46
80
  m.currency.should == Money::Currency.wrap("EUR")
47
81
  end
48
82
 
49
83
  it "accepts optional bank" do
50
- m = Money.new_with_dollars(1)
84
+ m = Money.new_with_amount(1)
51
85
  m.bank.should == Money.default_bank
52
86
 
53
- m = Money.new_with_dollars(1, "EUR", bank = Object.new)
87
+ m = Money.new_with_amount(1, "EUR", bank = Object.new)
54
88
  m.bank.should == bank
55
89
  end
56
90
 
57
91
  it "is associated to the singleton instance of Bank::VariableExchange by default" do
58
- Money.new_with_dollars(0).bank.should be(Money::Bank::VariableExchange.instance)
92
+ Money.new_with_amount(0).bank.should be(Money::Bank::VariableExchange.instance)
59
93
  end
60
94
  end
61
95
 
@@ -90,36 +124,94 @@ describe Money do
90
124
  end
91
125
  end
92
126
 
93
-
94
127
  describe "#cents" do
95
- it "returns the amount of cents" do
96
- Money.new(1_00).cents.should == 1_00
97
- Money.new_with_dollars(1).cents.should == 1_00
128
+ it "is a synonym of #fractional" do
129
+ expectation = Money.new(0)
130
+ def expectation.fractional
131
+ "expectation"
132
+ end
133
+ expectation.cents.should == "expectation"
134
+ end
135
+ end
136
+
137
+ describe "#fractional" do
138
+ it "returns the amount in fractional unit" do
139
+ Money.new(1_00).fractional.should == 1_00
140
+ Money.new_with_amount(1).fractional.should == 1_00
98
141
  end
99
142
 
100
- it "stores cents as an integer regardless of what is passed into the constructor" do
143
+ it "stores fractional as an integer regardless of what is passed into the constructor" do
101
144
  [ Money.new(100), 1.to_money, 1.00.to_money, BigDecimal('1.00').to_money ].each do |m|
102
- m.cents.should == 100
103
- m.cents.should be_a(Fixnum)
145
+ m.fractional.should == 100
146
+ m.fractional.should be_a(Fixnum)
147
+ end
148
+ end
149
+
150
+ context "user changes rounding_mode" do
151
+ after do
152
+ Money.rounding_mode = BigDecimal::ROUND_HALF_EVEN
153
+ end
154
+
155
+ it "respects the rounding_mode" do
156
+ Money.rounding_mode = BigDecimal::ROUND_DOWN
157
+ Money.new(1.9).fractional.should == 1
158
+
159
+ Money.rounding_mode = BigDecimal::ROUND_UP
160
+ Money.new(1.1).fractional.should == 2
161
+ end
162
+ end
163
+
164
+ context "infinite_precision = true" do
165
+ before do
166
+ Money.infinite_precision = true
167
+ end
168
+
169
+ after do
170
+ Money.infinite_precision = false
171
+ end
172
+
173
+ it "returns the amount in fractional unit" do
174
+ Money.new(1_00).fractional.should == BigDecimal("100")
175
+ Money.new_with_amount(1).fractional.should == BigDecimal("100")
176
+ end
177
+
178
+ it "stores in fractional unit as an integer regardless of what is passed into the constructor" do
179
+ [ Money.new(100), 1.to_money, 1.00.to_money, BigDecimal('1.00').to_money ].each do |m|
180
+ m.fractional.should == BigDecimal("100")
181
+ m.fractional.should be_a(BigDecimal)
182
+ end
104
183
  end
105
184
  end
106
185
  end
107
186
 
108
- describe "#dollars" do
187
+ describe "#amount" do
109
188
  it "returns the amount of cents as dollars" do
110
- Money.new(1_00).dollars.should == 1
111
- Money.new_with_dollars(1).dollars.should == 1
189
+ Money.new(1_00).amount.should == 1
190
+ Money.new_with_amount(1).amount.should == 1
112
191
  end
113
192
 
114
193
  it "respects :subunit_to_unit currency property" do
115
- Money.new(1_00, "USD").dollars.should == 1
116
- Money.new(1_000, "TND").dollars.should == 1
117
- Money.new(1, "CLP").dollars.should == 1
194
+ Money.new(1_00, "USD").amount.should == 1
195
+ Money.new(1_000, "TND").amount.should == 1
196
+ Money.new(1, "CLP").amount.should == 1
118
197
  end
119
198
 
120
199
  it "does not loose precision" do
121
- Money.new(100_37).dollars.should == 100.37
122
- Money.new_with_dollars(100.37).dollars.should == 100.37
200
+ Money.new(100_37).amount.should == 100.37
201
+ Money.new_with_amount(100.37).amount.should == 100.37
202
+ end
203
+ end
204
+
205
+ describe "#dollars" do
206
+ it "is synonym of #amount" do
207
+ m = Money.new(0)
208
+
209
+ # Make a small expectation
210
+ def m.amount
211
+ 5
212
+ end
213
+
214
+ m.dollars.should == 5
123
215
  end
124
216
  end
125
217
 
@@ -196,6 +288,24 @@ describe Money do
196
288
  it "respects :decimal_mark" do
197
289
  Money.new(10_00, "BRL").to_s.should == "10,00"
198
290
  end
291
+
292
+ context "infinite_precision = true" do
293
+ before do
294
+ Money.infinite_precision = true
295
+ end
296
+
297
+ after do
298
+ Money.infinite_precision = false
299
+ end
300
+
301
+ it "shows fractional cents" do
302
+ Money.new(1.05, "USD").to_s.should == "0.0105"
303
+ end
304
+
305
+ it "suppresses fractional cents when there is none" do
306
+ Money.new(1.0, "USD").to_s.should == "0.01"
307
+ end
308
+ end
199
309
  end
200
310
 
201
311
  describe "#to_d" do
@@ -280,6 +390,25 @@ describe Money do
280
390
  it "requires total to be less then 1" do
281
391
  expect { Money.us_dollar(0.05).allocate([0.5, 0.6]) }.to raise_error(ArgumentError)
282
392
  end
393
+
394
+ context "infinite_precision = true" do
395
+ before do
396
+ Money.infinite_precision = true
397
+ end
398
+
399
+ after do
400
+ Money.infinite_precision = false
401
+ end
402
+
403
+ it "allows for fractional cents allocation" do
404
+ one_third = BigDecimal("1") / BigDecimal("3")
405
+
406
+ moneys = Money.new(100).allocate([one_third, one_third, one_third])
407
+ moneys[0].cents.should == one_third * BigDecimal("100")
408
+ moneys[1].cents.should == one_third * BigDecimal("100")
409
+ moneys[2].cents.should == one_third * BigDecimal("100")
410
+ end
411
+ end
283
412
  end
284
413
 
285
414
  describe "#split" do
@@ -306,7 +435,24 @@ describe Money do
306
435
  moneys[1].cents.should == 33
307
436
  moneys[2].cents.should == 33
308
437
  end
309
- end
310
438
 
311
- end
439
+ context "infinite_precision = true" do
440
+ before do
441
+ Money.infinite_precision = true
442
+ end
443
+
444
+ after do
445
+ Money.infinite_precision = false
446
+ end
447
+
448
+ it "allows for splitting by fractional cents" do
449
+ thirty_three_and_one_third = BigDecimal("100") / BigDecimal("3")
312
450
 
451
+ moneys = Money.new(100).split(3)
452
+ moneys[0].cents.should == thirty_three_and_one_third
453
+ moneys[1].cents.should == thirty_three_and_one_third
454
+ moneys[2].cents.should == thirty_three_and_one_third
455
+ end
456
+ end
457
+ end
458
+ end