money 5.0.0 → 5.1.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
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