cashish 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,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
+