cashish 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010-2011 [Matthew Rudy Jacobs and Thought Sauce]
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,22 @@
1
+ Cashish
2
+ =======
3
+
4
+ Storing Currency as Integers
5
+ Cashish (will) make it easy to display this with the right number of decimal places.
6
+ As well as creating a simple "Amount" object you can perform arithmetic on.
7
+
8
+ Example
9
+ =======
10
+
11
+ gbp = Cashish::Amount.new(1_000_000_00, "GBP")
12
+
13
+ gbp.to_s
14
+ => "1,000,000.00 GBP"
15
+
16
+ gbp.to_f
17
+ => 1_000_000.00
18
+
19
+ gbp.to_s(:simple)
20
+ => "1000000.00"
21
+
22
+
@@ -0,0 +1,8 @@
1
+ require 'cashish/amount'
2
+ require 'cashish/currency'
3
+
4
+ module Cashish
5
+
6
+ class MissingCurrencyException < ArgumentError ; end
7
+
8
+ end
@@ -0,0 +1,139 @@
1
+ require 'active_support/core_ext/array/extract_options'
2
+
3
+ require 'action_view/helpers/number_helper'
4
+
5
+ # these are all for the number helper
6
+ # today, write this myself and remove these dependencies
7
+ require 'active_support/core_ext/hash/indifferent_access'
8
+ require 'active_support/core_ext/hash/reverse_merge'
9
+ require 'active_support/core_ext/string/output_safety'
10
+ require 'i18n'
11
+
12
+ module Cashish
13
+ class Amount
14
+ include Comparable
15
+ include ActionView::Helpers::NumberHelper
16
+
17
+ def initialize(integer_value, currency_code)
18
+ @integer_value = integer_value
19
+ @currency_code = currency_code
20
+ @currency_data = Currency[currency_code]
21
+ end
22
+ attr_reader :integer_value, :currency_code, :currency_data
23
+
24
+ def inspect
25
+ "#<Cashish::Amount #{self.to_s}>"
26
+ end
27
+
28
+ def <=>(other)
29
+ if self.currency_code == other.currency_code
30
+ self.integer_value <=> other.integer_value
31
+ end
32
+ end
33
+
34
+ def +(other)
35
+ if @currency_code == other.currency_code
36
+ Cashish::Amount.new(@integer_value.to_f + other.integer_value.to_f, @currency_code)
37
+ else
38
+ raise "you can't add different currencies"
39
+ end
40
+ end
41
+
42
+ def -(other)
43
+ if @currency_code == other.currency_code
44
+ Cashish::Amount.new(@integer_value.to_f - other.integer_value.to_f, @currency_code)
45
+ else
46
+ raise "you can't subtract different currencies"
47
+ end
48
+ end
49
+
50
+ def *(value)
51
+ if value.is_a?(Numeric)
52
+ if @integer_value
53
+ Cashish::Amount.new(@integer_value*value, @currency_code)
54
+ else
55
+ self
56
+ end
57
+ else
58
+ raise "you can only multiply a currency by a numeric"
59
+ end
60
+ end
61
+
62
+ def /(value)
63
+ if value.is_a?(Numeric)
64
+ if @integer_value
65
+ Cashish::Amount.new(@integer_value/value, @currency_code)
66
+ else
67
+ self
68
+ end
69
+ elsif value.is_a?(Cashish::Amount)
70
+ ratio(value)
71
+ else
72
+ raise "you can only divide a currency by a numeric or another currency"
73
+ end
74
+ end
75
+
76
+ def decimal_places
77
+ currency_data[:e]
78
+ end
79
+
80
+ def decimal_value
81
+ if integer_value
82
+ @decimal_value ||= integer_value * BigDecimal.new("1E-#{decimal_places}")
83
+ end
84
+ end
85
+
86
+ # use XE.com's format
87
+ # eg. 14,399.43 USD
88
+ def to_s(*args)
89
+ opts = args.extract_options!
90
+ format = args.first || :full
91
+
92
+ case format
93
+ when :full # USD 14,399.43
94
+ "#{formatted_value(opts) || "-"} #{currency_code}"
95
+ when :hide_currency # 14,399.43
96
+ "#{formatted_value(opts) || "-"}"
97
+ when :simple # 14399.43
98
+ formatted_value(opts.merge(:delimiter => "", :default_to_zero => true))
99
+ else
100
+ raise "bad format"
101
+ end
102
+ end
103
+
104
+ # the underlying value explicitly as an integer
105
+ def to_i
106
+ self.integer_value.to_i
107
+ end
108
+
109
+ # the underlying value explicitly as a float
110
+ def to_f
111
+ self.integer_value.to_f
112
+ end
113
+
114
+ def formatted_value(opts={})
115
+ this_value = decimal_value
116
+ if opts.delete(:default_to_zero)
117
+ this_value ||= 0
118
+ end
119
+
120
+ if this_value
121
+ precision = opts[:precision] || self.decimal_places
122
+ with_precision = "%01.#{precision}f" % this_value
123
+ number_with_delimiter(with_precision, {:separator => '.', :delimiter => ','}.merge(opts))
124
+ end
125
+ end
126
+
127
+ protected
128
+
129
+ # the ratio of two currencies
130
+ def ratio(other)
131
+ if @currency_code == other.currency_code
132
+ @integer_value.to_f / other.integer_value.to_f
133
+ else
134
+ raise "you can't find the ratio of two different currencies"
135
+ end
136
+ end
137
+
138
+ end
139
+ end
@@ -0,0 +1,33 @@
1
+ require 'active_support/memoizable'
2
+
3
+ module Cashish
4
+
5
+ module Currency
6
+
7
+ # http://en.wikipedia.org/wiki/ISO_4217
8
+ # Code | Num | E | Currency | Locations using this currency
9
+ # HKD | 344 | 2 | Hong Kong dollar | Hong Kong Special Administrative Region
10
+ CURRENCIES = [
11
+ {:code => "HKD", :num => 344, :e => 2, :currency => "Hong Kong dollar", :locations => "Hong Kong Special Administrative Region"},
12
+ {:code => "SGD", :num => 702, :e => 2, :currency => "Singapore dollar", :locations => "Singapore"}
13
+ ]
14
+
15
+ class << self
16
+
17
+ extend ActiveSupport::Memoizable
18
+
19
+ def find_by_code(code)
20
+ CURRENCIES.detect{|currency| currency[:code] == code} || raise(MissingCurrencyException, "no currency #{code}")
21
+ end
22
+ memoize :find_by_code
23
+
24
+ alias :[] :find_by_code
25
+
26
+ def codes
27
+ CURRENCIES.map{|currency| currency[:code]}
28
+ end
29
+ end
30
+
31
+ end
32
+
33
+ end
@@ -0,0 +1,70 @@
1
+ require 'test_helper'
2
+
3
+ class ArithmeticTest < Test::Unit::TestCase
4
+
5
+ test "addition" do
6
+ sum = Cashish::Amount.new(12_34, "HKD") + Cashish::Amount.new(500_00, "HKD")
7
+ assert_instance_of Cashish::Amount, sum
8
+ assert_equal "512.34 HKD", sum.to_s
9
+ end
10
+
11
+ test "addition to nil" do
12
+ sum = Cashish::Amount.new(nil, "HKD") + Cashish::Amount.new(500_00, "HKD")
13
+ assert_instance_of Cashish::Amount, sum
14
+ assert_equal "500.00 HKD", sum.to_s
15
+ end
16
+
17
+ test "addition of nil" do
18
+ sum = Cashish::Amount.new(12_34, "HKD") + Cashish::Amount.new(nil, "HKD")
19
+ assert_instance_of Cashish::Amount, sum
20
+ assert_equal "12.34 HKD", sum.to_s
21
+ end
22
+
23
+ test "subtraction" do
24
+ sum = Cashish::Amount.new(123_45, "HKD") - Cashish::Amount.new(21_00, "HKD")
25
+ assert_instance_of Cashish::Amount, sum
26
+ assert_equal "102.45 HKD", sum.to_s
27
+ end
28
+
29
+ test "subtraction from nil" do
30
+ sum = Cashish::Amount.new(nil, "HKD") - Cashish::Amount.new(21_00, "HKD")
31
+ assert_instance_of Cashish::Amount, sum
32
+ assert_equal "-21.00 HKD", sum.to_s
33
+ end
34
+
35
+ test "subtraction of nil" do
36
+ sum = Cashish::Amount.new(123_45, "HKD") - Cashish::Amount.new(nil, "HKD")
37
+ assert_instance_of Cashish::Amount, sum
38
+ assert_equal "123.45 HKD", sum.to_s
39
+ end
40
+
41
+ test "multiplication" do
42
+ sum = Cashish::Amount.new(12_34, "HKD") *4
43
+ assert_instance_of Cashish::Amount, sum
44
+ assert_equal "49.36 HKD", sum.to_s
45
+ end
46
+
47
+ test "multiplication of nil" do
48
+ it = Cashish::Amount.new(nil, "HKD")
49
+ sum = it * 4
50
+ assert_equal "- HKD", sum.to_s
51
+ end
52
+
53
+ test "division" do
54
+ sum = Cashish::Amount.new(12_34, "HKD") / 2
55
+ assert_instance_of Cashish::Amount, sum
56
+ assert_equal "6.17 HKD", sum.to_s
57
+ end
58
+
59
+ test "division of nil" do
60
+ it = Cashish::Amount.new(nil, "HKD")
61
+ sum = it / 4
62
+ assert_equal "- HKD", sum.to_s
63
+ end
64
+
65
+ test "division of two currencies" do
66
+ sum = Cashish::Amount.new(123_45, "HKD") / Cashish::Amount.new(21_00, "HKD")
67
+ assert_instance_of Float, sum
68
+ assert_equal_float 123_45.0 / 21_00.0, sum
69
+ end
70
+ end
@@ -0,0 +1,17 @@
1
+ require 'test_helper'
2
+
3
+ class CurrencyTest < Test::Unit::TestCase
4
+
5
+ test "Currency['HKD'] grabs all the data for HKDs" do
6
+ assert_equal(
7
+ {:code => "HKD", :num => 344, :e => 2, :currency => "Hong Kong dollar", :locations => "Hong Kong Special Administrative Region"},
8
+ Cashish::Currency["HKD"])
9
+ end
10
+
11
+ test "Currency[] raises an exception for an imaginary currency" do
12
+ assert_raise(Cashish::MissingCurrencyException) do
13
+ Cashish::Currency["notreal"]
14
+ end
15
+ end
16
+
17
+ end
@@ -0,0 +1,118 @@
1
+ require 'test_helper'
2
+
3
+ class DisplayTest < Test::Unit::TestCase
4
+
5
+ test "Cashish::Amount" do
6
+ amount = Cashish::Amount.new(12_345_67, "HKD")
7
+ assert_equal "HKD", amount.currency_code
8
+ assert_equal 2, amount.decimal_places
9
+ assert_equal "12,345.67", amount.formatted_value
10
+ assert_equal "12,345.67 HKD", amount.to_s
11
+ assert_equal "12,345.67 HKD", amount.to_s(:full)
12
+ assert_equal "12345.67", amount.to_s(:simple)
13
+
14
+ assert_equal 12_345_67, amount.integer_value
15
+ assert_equal 12_345.67, amount.decimal_value
16
+ assert_equal 12_345_67.0, amount.to_f
17
+ assert_equal 12_345_67, amount.to_i
18
+ end
19
+
20
+ test "Cashish::Amount - negative" do
21
+ amount = Cashish::Amount.new(-12_345_67, "HKD")
22
+ assert_equal "HKD", amount.currency_code
23
+ assert_equal 2, amount.decimal_places
24
+ assert_equal "-12,345.67", amount.formatted_value
25
+ assert_equal "-12,345.67 HKD", amount.to_s
26
+ assert_equal "-12,345.67 HKD", amount.to_s(:full)
27
+ assert_equal "-12345.67", amount.to_s(:simple)
28
+
29
+ assert_equal -12_345_67, amount.integer_value
30
+ assert_equal -12_345.67, amount.decimal_value
31
+ assert_equal -12_345_67.0, amount.to_f
32
+ assert_equal -12_345_67, amount.to_i
33
+ end
34
+
35
+ test "Cashish::Amount - nil" do
36
+ amount = Cashish::Amount.new(nil, "HKD")
37
+ assert_equal "HKD", amount.currency_code
38
+ assert_equal 2, amount.decimal_places
39
+ assert_equal nil, amount.formatted_value
40
+ assert_equal "0.00", amount.formatted_value(:default_to_zero => true)
41
+ assert_equal "- HKD", amount.to_s
42
+ assert_equal "- HKD", amount.to_s(:full)
43
+ assert_equal "0.00", amount.to_s(:simple)
44
+
45
+ assert_equal nil, amount.integer_value
46
+ assert_equal nil, amount.decimal_value
47
+ assert_equal_float 0.0, amount.to_f
48
+ assert_equal 0, amount.to_i
49
+ end
50
+
51
+ test "Cashish::Amount - float" do
52
+ amount = Cashish::Amount.new(123_456_78.9012, "HKD")
53
+ assert_equal "HKD", amount.currency_code
54
+ assert_equal 2, amount.decimal_places
55
+ assert_equal "123,456.79", amount.formatted_value
56
+ assert_equal "123,456.79 HKD", amount.to_s
57
+ assert_equal "123,456.79 HKD", amount.to_s(:full)
58
+ assert_equal "123456.79", amount.to_s(:simple)
59
+
60
+ assert_equal 123_456_78.9012, amount.integer_value
61
+ assert_equal_float 123_456.789012, amount.decimal_value
62
+
63
+ assert_equal_float amount.integer_value, amount.to_f
64
+ assert_equal amount.integer_value.to_i, amount.to_i
65
+ end
66
+
67
+ test "Cashish::Amount#to_s should accept a precision" do
68
+ amount = Cashish::Amount.new(123_45.678901234, "HKD")
69
+ assert_equal "123.46 HKD", amount.to_s
70
+ assert_equal "123.4568 HKD", amount.to_s(:precision => 4)
71
+ end
72
+
73
+ test "Cashish::Amount#to_s should accept delimiter and separator" do
74
+ amount = Cashish::Amount.new(123_456_789_012_34, "HKD")
75
+ assert_equal "123,456,789,012.34 HKD", amount.to_s
76
+ assert_equal "123o456o789o012-34 HKD", amount.to_s(:delimiter => "o", :separator => "-")
77
+ end
78
+
79
+ test "Cashish::Amount#to_s should round to the nearest if given a float" do
80
+ assert_equal "0.02 HKD", Cashish::Amount.new(5/3.0, "HKD").to_s # 0.016666
81
+ assert_equal "0.02 HKD", Cashish::Amount.new(7/3.0, "HKD").to_s # 0.023333
82
+ end
83
+
84
+ test "Cashish::Amount#to_s should handle massive numbers" do
85
+ assert_to_s(
86
+ 100_000_000_000_000_000_000_00,
87
+ "100,000,000,000,000,000,000.00 HKD")
88
+ end
89
+
90
+ test "Cashish::Amount#to_s should keep all significant figures" do
91
+ assert_to_s(123_456_789_012_345_67, "123,456,789,012,345.67 HKD")
92
+ end
93
+
94
+ test "ruby patchlevel 173 bug" do
95
+ assert_to_s(100_000_01, "100,000.01 HKD")
96
+ end
97
+
98
+ test "simple edge cases" do
99
+ assert_to_s(1_00, "1.00 HKD")
100
+ assert_to_s( 1, "0.01 HKD")
101
+ assert_to_s( 10, "0.10 HKD")
102
+ end
103
+
104
+ test "negative numbers" do
105
+ assert_to_s( -1, "-0.01 HKD")
106
+ assert_to_s( -12, "-0.12 HKD")
107
+ assert_to_s( -123, "-1.23 HKD")
108
+
109
+ assert_to_s(-123456, "-1,234.56 HKD")
110
+ end
111
+
112
+ protected
113
+
114
+ def assert_to_s(value, expected_to_s)
115
+ assert_equal expected_to_s, Cashish::Amount.new(value,"HKD").to_s, "value of #{value.inspect} came out wrong"
116
+ end
117
+
118
+ end
@@ -0,0 +1,15 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'active_support/testing/declarative'
4
+
5
+ $: << File.expand_path(File.dirname(__FILE__)+"/../lib")
6
+ require 'cashish'
7
+
8
+ class Test::Unit::TestCase
9
+ extend ActiveSupport::Testing::Declarative
10
+
11
+ def assert_equal_float(expected, actual, message=nil)
12
+ assert_in_delta expected, actual, 1E-4, message
13
+ end
14
+
15
+ end
metadata ADDED
@@ -0,0 +1,101 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cashish
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Matthew Rudy Jacobs
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-04-22 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: activesupport
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 3
29
+ segments:
30
+ - 0
31
+ version: "0"
32
+ type: :runtime
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: actionpack
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ hash: 3
43
+ segments:
44
+ - 0
45
+ version: "0"
46
+ type: :runtime
47
+ version_requirements: *id002
48
+ description:
49
+ email: MatthewRudyJacobs@gmail.com
50
+ executables: []
51
+
52
+ extensions: []
53
+
54
+ extra_rdoc_files:
55
+ - README
56
+ files:
57
+ - MIT-LICENSE
58
+ - test/arithmetic_test.rb
59
+ - test/currency_test.rb
60
+ - test/display_test.rb
61
+ - test/test_helper.rb
62
+ - lib/cashish/amount.rb
63
+ - lib/cashish/currency.rb
64
+ - lib/cashish.rb
65
+ - README
66
+ homepage: https://github.com/matthewrudy/cashish
67
+ licenses: []
68
+
69
+ post_install_message:
70
+ rdoc_options:
71
+ - --main
72
+ - README
73
+ require_paths:
74
+ - lib
75
+ required_ruby_version: !ruby/object:Gem::Requirement
76
+ none: false
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ hash: 3
81
+ segments:
82
+ - 0
83
+ version: "0"
84
+ required_rubygems_version: !ruby/object:Gem::Requirement
85
+ none: false
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ hash: 3
90
+ segments:
91
+ - 0
92
+ version: "0"
93
+ requirements: []
94
+
95
+ rubyforge_project:
96
+ rubygems_version: 1.7.2
97
+ signing_key:
98
+ specification_version: 3
99
+ summary: Currency Handling done simple
100
+ test_files: []
101
+