cash 0.0.2 → 0.1.0
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/.travis.yml +15 -0
- data/Guardfile +1 -1
- data/README.md +9 -0
- data/cash.gemspec +1 -1
- data/lib/cash.rb +89 -0
- data/lib/cash/currency.rb +5 -0
- data/spec/lib/cash/currency_spec.rb +16 -0
- data/spec/lib/cash_spec.rb +159 -0
- metadata +7 -4
data/.travis.yml
ADDED
data/Guardfile
CHANGED
data/README.md
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
Money object for your application. Based on BigDecimal and ISO-4217 currencies.
|
4
4
|
|
5
|
+
|
5
6
|
## Features
|
6
7
|
|
7
8
|
Encapsulates an amount with its currency.
|
@@ -10,6 +11,14 @@ Backed by BigDecimal, so it can store arbitrary-precision values without roundin
|
|
10
11
|
|
11
12
|
Safe and strict. Fail fast approach let's you sleep like a baby.
|
12
13
|
|
14
|
+
|
15
|
+
## Compatibility
|
16
|
+
|
17
|
+
Tested on Ruby 1.9, 1.8, JRuby, and RBX:
|
18
|
+
|
19
|
+
https://travis-ci.org/#!/pithyless/cash
|
20
|
+
|
21
|
+
|
13
22
|
## Credits
|
14
23
|
|
15
24
|
Huge thanks go out to:
|
data/cash.gemspec
CHANGED
@@ -4,7 +4,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
4
|
|
5
5
|
Gem::Specification.new do |gem|
|
6
6
|
gem.name = "cash"
|
7
|
-
gem.version = '0.0
|
7
|
+
gem.version = '0.1.0'
|
8
8
|
gem.authors = ["Norbert Wojtowicz"]
|
9
9
|
gem.email = ["wojtowicz.norbert@gmail.com"]
|
10
10
|
gem.description = "Money model backed by BigDecimal"
|
data/lib/cash.rb
CHANGED
@@ -1,5 +1,94 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'bigdecimal'
|
4
|
+
|
1
5
|
require "cash/currency"
|
2
6
|
require "cash/currency/iso4217"
|
3
7
|
|
4
8
|
class Cash
|
9
|
+
include Comparable
|
10
|
+
include Equalable
|
11
|
+
|
12
|
+
def initialize(amount, currency)
|
13
|
+
@amount, @currency = StrictDecimal(amount), Currency.find!(currency)
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :amount, :currency
|
17
|
+
|
18
|
+
def StrictDecimal(arg)
|
19
|
+
case arg
|
20
|
+
when BigDecimal
|
21
|
+
arg
|
22
|
+
when Float
|
23
|
+
fail ArgumentError, "innacurate float for StrictDecimal(): #{arg.inspect}"
|
24
|
+
when Integer
|
25
|
+
BigDecimal(arg.to_s)
|
26
|
+
when String
|
27
|
+
Float(arg)
|
28
|
+
BigDecimal(arg)
|
29
|
+
else
|
30
|
+
fail TypeError
|
31
|
+
end
|
32
|
+
rescue TypeError
|
33
|
+
fail ArgumentError, "invalid value for StrictDecimal(): #{arg.inspect}"
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_s
|
37
|
+
Format.display(self)
|
38
|
+
end
|
39
|
+
|
40
|
+
def inspect
|
41
|
+
"<Cash #{amount_string} #{currency.code}>"
|
42
|
+
end
|
43
|
+
|
44
|
+
def amount_string
|
45
|
+
"%.#{currency.offset}f" % amount.round(currency.offset)
|
46
|
+
end
|
47
|
+
|
48
|
+
def <=>(o)
|
49
|
+
check_type(o, :compare)
|
50
|
+
amount <=> o.amount
|
51
|
+
end
|
52
|
+
|
53
|
+
def +(o)
|
54
|
+
check_type(o, :add)
|
55
|
+
Cash.new(amount + o.amount, currency)
|
56
|
+
end
|
57
|
+
|
58
|
+
def -(o)
|
59
|
+
check_type(o, :subtract)
|
60
|
+
Cash.new(amount - o.amount, currency)
|
61
|
+
end
|
62
|
+
|
63
|
+
protected
|
64
|
+
|
65
|
+
def equality_state
|
66
|
+
[amount, currency]
|
67
|
+
end
|
68
|
+
|
69
|
+
def check_type(o, method)
|
70
|
+
unless o.instance_of?(self.class) && o.currency == currency
|
71
|
+
raise TypeError, "cannot #{method} #{self.inspect} to #{o.inspect}"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
module Format
|
77
|
+
CURRENCIES = {
|
78
|
+
'EUR' => '€',
|
79
|
+
'USD' => '$'
|
80
|
+
}.freeze
|
81
|
+
|
82
|
+
def self.display(cash)
|
83
|
+
amount = cash.amount_string
|
84
|
+
currency_code = cash.currency.code
|
85
|
+
|
86
|
+
if sym = CURRENCIES[currency_code]
|
87
|
+
"#{sym}#{amount}"
|
88
|
+
else
|
89
|
+
"#{amount} #{currency_code}"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
5
94
|
end
|
data/lib/cash/currency.rb
CHANGED
@@ -19,10 +19,15 @@ class Cash
|
|
19
19
|
class << self
|
20
20
|
|
21
21
|
def find(currency)
|
22
|
+
return currency if currency.kind_of?(self)
|
22
23
|
currency = currency.to_s.upcase
|
23
24
|
all.find{ |c| c.code == currency }
|
24
25
|
end
|
25
26
|
|
27
|
+
def find!(currency)
|
28
|
+
find(currency) or fail ArgumentError, "unknown currency: #{currency}"
|
29
|
+
end
|
30
|
+
|
26
31
|
def all
|
27
32
|
@all ||= Set.new
|
28
33
|
end
|
@@ -29,12 +29,28 @@ describe Cash::Currency do
|
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
32
|
+
it 'returns self for currency' do
|
33
|
+
currency = Cash::Currency::USD
|
34
|
+
Cash::Currency.find(currency).should equal(currency)
|
35
|
+
end
|
36
|
+
|
32
37
|
it 'returns nil for missing currency' do
|
33
38
|
Cash::Currency.find('zzz').should be_nil
|
34
39
|
end
|
35
40
|
end
|
36
41
|
|
37
42
|
|
43
|
+
describe '::find!' do
|
44
|
+
it 'returns currency' do
|
45
|
+
Cash::Currency.find!('usd').should == Cash::Currency::USD
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'fails for missing currency' do
|
49
|
+
expect{ Cash::Currency.find!('zzz') }.to raise_error(ArgumentError)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
|
38
54
|
context 'equality' do
|
39
55
|
let(:usd) { Cash::Currency::USD }
|
40
56
|
let(:usd2) { Cash::Currency.new('usd', 2, 'US dollar') }
|
@@ -0,0 +1,159 @@
|
|
1
|
+
#encoding: UTF-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Cash do
|
6
|
+
|
7
|
+
describe '#initialize' do
|
8
|
+
|
9
|
+
let(:currency) { Cash::Currency::USD }
|
10
|
+
let(:big_one) { BigDecimal('1.0') }
|
11
|
+
let(:one) { '1.0' }
|
12
|
+
|
13
|
+
context 'amount' do
|
14
|
+
it 'initializes with big decimal' do
|
15
|
+
Cash.new(big_one, currency).amount.should eq(big_one)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'initializes with string' do
|
19
|
+
Cash.new(one, currency).amount.should eq(big_one)
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'initializes with integer' do
|
23
|
+
Cash.new(1, currency).amount.should eq(big_one)
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'fails with float' do
|
27
|
+
expect{ Cash.new(Float(one), currency) }.to raise_error(ArgumentError)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'fails with nil object' do
|
31
|
+
expect{ Cash.new(nil, currency) }.to raise_error(ArgumentError)
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'fails with other object' do
|
35
|
+
expect{ Cash.new(Object.new, currency) }.to raise_error(ArgumentError)
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'fails with invalid string' do
|
39
|
+
expect{ Cash.new('silly', currency) }.to raise_error(ArgumentError)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context 'currency' do
|
44
|
+
it 'initializes with string' do
|
45
|
+
Cash.new(1, 'usd').currency.should == Cash::Currency::USD
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'initializes with symbol' do
|
49
|
+
Cash.new(1, :USD).currency.should == Cash::Currency::USD
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'initializes with currency' do
|
53
|
+
currency = Cash::Currency.find('usd')
|
54
|
+
Cash.new(1, currency).currency.should == Cash::Currency::USD
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'fails with missing currency' do
|
58
|
+
expect{ Cash.new(1, 'zzz') }.to raise_error(ArgumentError)
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'fails with nil object' do
|
62
|
+
expect{ Cash.new(1, nil) }.to raise_error(ArgumentError)
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'fails with other object' do
|
66
|
+
expect{ Cash.new(1, Object.new) }.to raise_error(ArgumentError)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
describe '#to_s' do
|
73
|
+
it 'displays Euro' do
|
74
|
+
Cash.new('100.3', :eur).to_s.should == '€100.30'
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'displays Dollar' do
|
78
|
+
Cash.new('9999', :usd).to_s.should == '$9999.00'
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'displays other currencies' do
|
82
|
+
Cash.new('.123', :pln).to_s.should == '0.12 PLN'
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'displays correct offset' do
|
86
|
+
Cash.new('123.4567', :jpy).to_s.should == '123 JPY'
|
87
|
+
Cash.new('123.4567', :twd).to_s.should == '123.5 TWD'
|
88
|
+
Cash.new('123.4567', :pln).to_s.should == '123.46 PLN'
|
89
|
+
Cash.new('123.4567', :bhd).to_s.should == '123.457 BHD'
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
describe '#inspect' do
|
94
|
+
it 'displays value and currency' do
|
95
|
+
Cash.new('100.3', :eur).inspect.should == '<Cash 100.30 EUR>'
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
context 'comparable' do
|
100
|
+
describe '#==' do
|
101
|
+
it { Cash.new(1, :usd).should == Cash.new(1, :usd) }
|
102
|
+
it { Cash.new(2, :usd).should_not == Cash.new(1, :usd) }
|
103
|
+
it { Cash.new(1, :pln).should_not == Cash.new(1, :usd) }
|
104
|
+
end
|
105
|
+
|
106
|
+
describe '#<=>' do
|
107
|
+
it { (Cash.new(1, :usd) <=> Cash.new(2, :usd)).should == -1 }
|
108
|
+
it { (Cash.new(2, :usd) <=> Cash.new(2, :usd)).should == 0 }
|
109
|
+
it { (Cash.new(3, :usd) <=> Cash.new(2, :usd)).should == 1 }
|
110
|
+
|
111
|
+
it 'fails for different currencies' do
|
112
|
+
expect{ Cash.new(1, :pln) <=> Cash.new(1, :usd) }.to raise_error(TypeError)
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'fails for non-cash' do
|
116
|
+
expect{ Cash.new(1, :pln) <=> 1 }.to raise_error(TypeError)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
it { (Cash.new(1, :usd) < Cash.new(2, :usd)).should be_true }
|
121
|
+
it { (Cash.new(3, :usd) >= Cash.new(3, :usd)).should be_true }
|
122
|
+
it { (Cash.new(3, :usd) == Cash.new(3, :usd)).should be_true }
|
123
|
+
it { (Cash.new(3, :pln) != Cash.new(3, :usd)).should be_true }
|
124
|
+
it { (Cash.new('1.234', :usd) != Cash.new('1.23', :usd)).should be_true }
|
125
|
+
|
126
|
+
it 'fails for different currencies' do
|
127
|
+
expect{ Cash.new(1, :usd) < Cash.new(1, :pln) }.to raise_error(TypeError)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
context 'arithmetic' do
|
132
|
+
let(:one_usd) { Cash.new(1, :usd) }
|
133
|
+
let(:two_usd) { Cash.new(2, :usd) }
|
134
|
+
let(:three_usd) { Cash.new(3, :usd) }
|
135
|
+
let(:two_pln) { Cash.new(2, :pln) }
|
136
|
+
|
137
|
+
|
138
|
+
describe 'addition' do
|
139
|
+
it 'adds amounts' do
|
140
|
+
(one_usd + two_usd).should == three_usd
|
141
|
+
end
|
142
|
+
|
143
|
+
it 'fails for different currencies' do
|
144
|
+
expect{ one_usd + two_pln }.to raise_error(TypeError)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
describe 'subtraction' do
|
149
|
+
it 'subtracts amounts' do
|
150
|
+
(three_usd - one_usd).should == two_usd
|
151
|
+
end
|
152
|
+
|
153
|
+
it 'fails for different currencies' do
|
154
|
+
expect{ three_usd - two_pln }.to raise_error(TypeError)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cash
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-10-
|
12
|
+
date: 2012-10-08 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rake
|
@@ -51,6 +51,7 @@ extensions: []
|
|
51
51
|
extra_rdoc_files: []
|
52
52
|
files:
|
53
53
|
- .gitignore
|
54
|
+
- .travis.yml
|
54
55
|
- Gemfile
|
55
56
|
- Guardfile
|
56
57
|
- LICENSE.txt
|
@@ -62,6 +63,7 @@ files:
|
|
62
63
|
- lib/cash/currency/iso4217.rb
|
63
64
|
- lib/cash/equalable.rb
|
64
65
|
- spec/lib/cash/currency_spec.rb
|
66
|
+
- spec/lib/cash_spec.rb
|
65
67
|
- spec/spec_helper.rb
|
66
68
|
homepage: https://github.com/pithyless/cash
|
67
69
|
licenses: []
|
@@ -77,7 +79,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
77
79
|
version: '0'
|
78
80
|
segments:
|
79
81
|
- 0
|
80
|
-
hash:
|
82
|
+
hash: 2451445660094864039
|
81
83
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
82
84
|
none: false
|
83
85
|
requirements:
|
@@ -86,7 +88,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
86
88
|
version: '0'
|
87
89
|
segments:
|
88
90
|
- 0
|
89
|
-
hash:
|
91
|
+
hash: 2451445660094864039
|
90
92
|
requirements: []
|
91
93
|
rubyforge_project:
|
92
94
|
rubygems_version: 1.8.23
|
@@ -95,4 +97,5 @@ specification_version: 3
|
|
95
97
|
summary: Money model backed by BigDecimal
|
96
98
|
test_files:
|
97
99
|
- spec/lib/cash/currency_spec.rb
|
100
|
+
- spec/lib/cash_spec.rb
|
98
101
|
- spec/spec_helper.rb
|