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.
@@ -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