cash 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,15 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 1.9.2
5
+ - jruby-18mode
6
+ - jruby-19mode
7
+ - rbx-18mode
8
+ - rbx-19mode
9
+ - ruby-head
10
+ - jruby-head
11
+ - 1.8.7
12
+ - ree
13
+ matrix:
14
+ allow_failures:
15
+ - rvm: jruby-head
data/Guardfile CHANGED
@@ -1,5 +1,5 @@
1
1
  guard 'rspec', :version => 2, :cli => '--color' do
2
2
  watch(%r{^spec/.+_spec\.rb$})
3
- watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
3
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
4
4
  watch('spec/spec_helper.rb') { "spec" }
5
5
  end
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:
@@ -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.2'
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"
@@ -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
@@ -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.2
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-02 00:00:00.000000000 Z
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: 2019834350230060789
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: 2019834350230060789
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