monetary_value 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in monetary_value.gemspec
4
+ gemspec
data/README.rdoc ADDED
@@ -0,0 +1,6 @@
1
+ = Monetary Value -- Library for dealing with monetary values
2
+
3
+ This library provides the classes MonetaryValue and Currency.
4
+
5
+
6
+ Copyright (c) 2009 OpenCuro, Inc., all rights reserved
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rake/testtask'
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.libs << 'test'
7
+ t.pattern = 'test/**/*_test.rb'
8
+ end
9
+
10
+ desc "Run tests"
11
+ task :default => :test
data/lib/currency.rb ADDED
@@ -0,0 +1,82 @@
1
+ require 'active_support/core_ext/enumerable' # Provides Enumerable#index_by
2
+
3
+ class Currency
4
+ attr_reader :id, :name, :code, :minor_divisor
5
+
6
+ def initialize(id, name, code, minor_divisor)
7
+ @id = id
8
+ @name = name
9
+ @code = code
10
+ @minor_divisor = minor_divisor
11
+ end
12
+
13
+ unless const_defined?(:CURRENCIES)
14
+ CURRENCIES = [
15
+ new(840, "United States Dollar", "USD", 100),
16
+ ]
17
+ CURRENCIES_BY_ID = CURRENCIES.index_by(&:id)
18
+ CURRENCIES_BY_NAME = CURRENCIES.index_by(&:name)
19
+ CURRENCIES_BY_CODE = CURRENCIES.index_by(&:code)
20
+
21
+ CURRENCIES.freeze
22
+ CURRENCIES_BY_ID.freeze
23
+ CURRENCIES_BY_NAME.freeze
24
+ CURRENCIES_BY_CODE.freeze
25
+ end
26
+
27
+ def convert_to_minor_denomination(major_denomination)
28
+ return nil if major_denomination.nil?
29
+ (major_denomination.to_f * self.minor_divisor.to_f).round
30
+ end
31
+
32
+ def convert_to_major_denomination(minor_denomination)
33
+ return nil if minor_denomination.nil?
34
+ minor_denomination.to_f / self.minor_divisor.to_f
35
+ end
36
+
37
+ class << self
38
+ def all
39
+ CURRENCIES
40
+ end
41
+
42
+ def new(id = 840)
43
+ self[id]
44
+ end
45
+
46
+ def [](arg)
47
+ case arg
48
+ when Integer
49
+ CURRENCIES_BY_ID[arg]
50
+ else
51
+ raise ArgumentError, "invalid argument to Currency[]: #{arg.inspect}"
52
+ end
53
+ end
54
+
55
+ def typecast(object)
56
+ return nil if object.nil?
57
+ return object if object.is_a? self
58
+
59
+ case object
60
+ when Integer
61
+ currency = CURRENCIES_BY_ID[object]
62
+ currency.nil? ? Currency.new : currency
63
+ when String
64
+ currency = CURRENCIES_BY_CODE[object]
65
+ currency.nil? ? Currency.new : currency
66
+ else
67
+ raise UntypecastableArgumentError, "#{object.inspect}:#{object.class} cannot be cast as #{self}"
68
+ end
69
+ end
70
+
71
+ end
72
+
73
+ def new_record?
74
+ false
75
+ end
76
+
77
+ def destroyed?
78
+ false
79
+ end
80
+
81
+ end
82
+
@@ -0,0 +1,200 @@
1
+ require 'monetary_value/version'
2
+
3
+ require 'currency'
4
+
5
+ class MonetaryValue
6
+ attr_reader :currency, :denomination
7
+
8
+ def initialize(denomination = 0, currency = Currency.new)
9
+ @denomination = denomination
10
+ @currency = currency
11
+ end
12
+
13
+ # Backwards compatibility
14
+ # TODO: Remove minor_denomination from MonetaryValue
15
+ def minor_denomination
16
+ denomination
17
+ end
18
+
19
+ def major_denomination
20
+ "%01.2f" % (currency.convert_to_major_denomination(denomination))
21
+ end
22
+
23
+ # Display
24
+ def to_s(format = :default)
25
+ case format
26
+ when :default, :short
27
+ "$#{major_denomination}"
28
+ when :long
29
+ "#{major_denomination} #{currency.code}"
30
+ else
31
+ "$#{major_denomination}"
32
+ end
33
+ end
34
+
35
+ def inspect
36
+ to_s(:long)
37
+ end
38
+
39
+ def self.parse(string)
40
+ currency = Currency.new
41
+ string.strip!
42
+ denomination = nil
43
+ nonnegative_string_formats = [
44
+ /^(\d+)$/, # "123"
45
+ /^\$(\d+)$/, # "$123"
46
+ /^(\d*)\.(\d{1,2})$/, # "123.4", ".4", "123.45", ".45"
47
+ /^\$(\d*)\.(\d{1,2})$/, # "$123.4", "$.4", "$123.45", "$.45"
48
+ ]
49
+
50
+ nonnegative_string_formats.each do |format|
51
+ result = string.scan(format)
52
+ if result.size == 1
53
+ denominations = result.first
54
+ major_denomination = denominations[0].to_i
55
+ minor_denomination = denominations[1].nil? ? 0 : denominations[1].ljust(2, "0").to_i
56
+ denomination = major_denomination * 100 + minor_denomination
57
+ return MonetaryValue.new(denomination, currency)
58
+ end
59
+ end
60
+
61
+ negative_string_formats = [
62
+ /^-(\d+)$/, # "-123"
63
+ /^-\$(\d+)$/, # "-$123"
64
+ /^\$-(\d+)$/, # "$-123"
65
+ /^-(\d*)\.(\d{1,2})$/, # "-123.4", "-.4", "-123.45", "-.45"
66
+ /^-\$(\d*)\.(\d{1,2})$/, # "-$123.4", "-$.4", "-$123.45", "-$.45"
67
+ /^\$-(\d*)\.(\d{1,2})$/, # "$-123.4", "$-.4", "$-123.45", "$-.45"
68
+ ]
69
+
70
+ negative_string_formats.each do |format|
71
+ result = string.scan(format)
72
+ if result.size == 1
73
+ denominations = result.first
74
+ major_denomination = denominations[0].to_i
75
+ minor_denomination = denominations[1].nil? ? 0 : denominations[1].ljust(2, "0").to_i
76
+ denomination = major_denomination * 100 + minor_denomination
77
+ return MonetaryValue.new(-denomination, currency)
78
+ end
79
+ end
80
+
81
+ raise ArgumentError, "invalid money_value"
82
+ end
83
+
84
+ class << self
85
+ def typecast(object)
86
+ return nil if object.nil?
87
+ return object if object.is_a? self
88
+
89
+ case object
90
+ when String
91
+ begin
92
+ parse(object)
93
+ rescue ArgumentError => e
94
+ raise UntypecastableArgumentError, "#{object.inspect}:#{object.class} cannot be cast as #{self}"
95
+ end
96
+ else
97
+ raise UntypecastableArgumentError, "#{object.inspect}:#{object.class} cannot be cast as #{self}"
98
+ end
99
+ end
100
+
101
+ end
102
+
103
+ ##############
104
+ # Arithmetic #
105
+ ##############
106
+
107
+ # Comparisons
108
+ include Comparable
109
+ def <=>(other)
110
+ raise ArgumentError, "can't compare values in difference currencies" unless self =~ other
111
+ denomination <=> other.denomination
112
+ end
113
+
114
+ # Addition
115
+ def +(other)
116
+ raise ArgumentError, "can't add values in difference currencies" unless self =~ other
117
+ MonetaryValue.new(denomination + other.denomination, currency)
118
+ end
119
+
120
+ # Subtraction
121
+ def -(other)
122
+ raise ArgumentError, "can't subtract values in difference currencies" unless self =~ other
123
+ MonetaryValue.new(denomination - other.denomination, currency)
124
+ end
125
+
126
+ # Multiplication
127
+ def *(num)
128
+ MonetaryValue.new(denomination * num, currency)
129
+ end
130
+
131
+ # Division
132
+ def /(num)
133
+ MonetaryValue.new(denomination / num, currency)
134
+ end
135
+
136
+ # Absolute value
137
+ def abs
138
+ MonetaryValue.new(denomination.abs, currency)
139
+ end
140
+
141
+ # Unary plus
142
+ def +@
143
+ MonetaryValue.new(denomination, currency)
144
+ end
145
+
146
+ # Unary minus
147
+ def -@
148
+ MonetaryValue.new(-denomination, currency)
149
+ end
150
+
151
+ # Currency matching
152
+ def =~(other)
153
+ currency == other.currency
154
+ end
155
+
156
+ # Next
157
+ def next
158
+ MonetaryValue.new(denomination.next, currency)
159
+ end
160
+ alias :succ :next
161
+
162
+ ##############
163
+ # Sign tests #
164
+ ##############
165
+
166
+ def zero?
167
+ denomination.zero?
168
+ end
169
+
170
+ def sign
171
+ denomination.sign
172
+ end
173
+
174
+ def positive?
175
+ denomination.positive?
176
+ end
177
+
178
+ def negative?
179
+ denomination.negative?
180
+ end
181
+
182
+ #################
183
+ # Serialization #
184
+ #################
185
+
186
+ def self.xml_root
187
+ "money"
188
+ end
189
+
190
+ def to_xml(options = {})
191
+ options[:root] ||= self.class.xml_root
192
+ { :currency_id => currency.id, :denomination => denomination }.to_xml(options)
193
+ end
194
+
195
+ def self.from_xml(xml)
196
+ attributes = Hash.from_xml(xml)[xml_root].symbolize_keys
197
+ new( attributes[:denomination] || attributes[:minor_value], Currency.new(attributes[:currency_id]) )
198
+ end
199
+
200
+ end
@@ -0,0 +1,3 @@
1
+ class MonetaryValue
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "monetary_value/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "monetary_value"
7
+ s.version = MonetaryValue::VERSION
8
+ s.authors = ["Ian James"]
9
+ s.email = ["ian@opencuro.com"]
10
+ s.homepage = "https://opencuro.com"
11
+ s.summary = %q{Library for working with monetary values}
12
+ s.description = %q{Provides the MonetaryValue and Currency classes.}
13
+
14
+ s.files = `git ls-files`.split("\n")
15
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
16
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
17
+ s.require_paths = ["lib"]
18
+
19
+ # specify any dependencies here; for example:
20
+ # s.add_development_dependency "rspec"
21
+ # s.add_runtime_dependency "rest-client"
22
+ s.add_dependency "activesupport"
23
+ end
@@ -0,0 +1,9 @@
1
+ require 'test_helper'
2
+ require 'currency'
3
+
4
+ class CurrencyTest < ActiveSupport::TestCase
5
+ # Replace this with your real tests.
6
+ test "the truth" do
7
+ assert true
8
+ end
9
+ end
@@ -0,0 +1,149 @@
1
+ require 'test_helper'
2
+ require 'monetary_value'
3
+
4
+ class MonetaryValueTest < ActiveSupport::TestCase
5
+ def test_parse
6
+ valid_strings = {
7
+ "0" => MonetaryValue.new(0),
8
+ "00" => MonetaryValue.new(0),
9
+ "1" => MonetaryValue.new(100),
10
+ "123" => MonetaryValue.new(12300),
11
+
12
+ "$0" => MonetaryValue.new(0),
13
+ "$00" => MonetaryValue.new(0),
14
+ "$1" => MonetaryValue.new(100),
15
+ "$123" => MonetaryValue.new(12300),
16
+
17
+ "0.00" => MonetaryValue.new(0),
18
+ "0.0" => MonetaryValue.new(0),
19
+ ".00" => MonetaryValue.new(0),
20
+ ".0" => MonetaryValue.new(0),
21
+ "0.12" => MonetaryValue.new(12),
22
+ "0.10" => MonetaryValue.new(10),
23
+ "0.1" => MonetaryValue.new(10),
24
+ ".12" => MonetaryValue.new(12),
25
+ ".10" => MonetaryValue.new(10),
26
+ ".1" => MonetaryValue.new(10),
27
+ "1.23" => MonetaryValue.new(123),
28
+ "1.20" => MonetaryValue.new(120),
29
+ "1.2" => MonetaryValue.new(120),
30
+
31
+ "$0.00" => MonetaryValue.new(0),
32
+ "$0.0" => MonetaryValue.new(0),
33
+ "$.00" => MonetaryValue.new(0),
34
+ "$.0" => MonetaryValue.new(0),
35
+ "$0.12" => MonetaryValue.new(12),
36
+ "$0.10" => MonetaryValue.new(10),
37
+ "$0.1" => MonetaryValue.new(10),
38
+ "$.12" => MonetaryValue.new(12),
39
+ "$.10" => MonetaryValue.new(10),
40
+ "$.1" => MonetaryValue.new(10),
41
+ "$1.23" => MonetaryValue.new(123),
42
+ "$1.20" => MonetaryValue.new(120),
43
+ "$1.2" => MonetaryValue.new(120),
44
+
45
+ "-0" => MonetaryValue.new(0),
46
+ "-00" => MonetaryValue.new(0),
47
+ "-1" => MonetaryValue.new(-100),
48
+ "-123" => MonetaryValue.new(-12300),
49
+
50
+ "-$0" => MonetaryValue.new(0),
51
+ "-$00" => MonetaryValue.new(0),
52
+ "-$1" => MonetaryValue.new(-100),
53
+ "-$123" => MonetaryValue.new(-12300),
54
+
55
+ "$-0" => MonetaryValue.new(0),
56
+ "$-00" => MonetaryValue.new(0),
57
+ "$-1" => MonetaryValue.new(-100),
58
+ "$-123" => MonetaryValue.new(-12300),
59
+
60
+ "-0.00" => MonetaryValue.new(0),
61
+ "-0.0" => MonetaryValue.new(0),
62
+ "-.00" => MonetaryValue.new(0),
63
+ "-.0" => MonetaryValue.new(0),
64
+ "-0.12" => MonetaryValue.new(-12),
65
+ "-0.10" => MonetaryValue.new(-10),
66
+ "-0.1" => MonetaryValue.new(-10),
67
+ "-.12" => MonetaryValue.new(-12),
68
+ "-.10" => MonetaryValue.new(-10),
69
+ "-.1" => MonetaryValue.new(-10),
70
+ "-1.23" => MonetaryValue.new(-123),
71
+ "-1.20" => MonetaryValue.new(-120),
72
+ "-1.2" => MonetaryValue.new(-120),
73
+
74
+ "-$0.00" => MonetaryValue.new(0),
75
+ "-$0.0" => MonetaryValue.new(0),
76
+ "-$.00" => MonetaryValue.new(0),
77
+ "-$.0" => MonetaryValue.new(0),
78
+ "-$0.12" => MonetaryValue.new(-12),
79
+ "-$0.10" => MonetaryValue.new(-10),
80
+ "-$0.1" => MonetaryValue.new(-10),
81
+ "-$.12" => MonetaryValue.new(-12),
82
+ "-$.10" => MonetaryValue.new(-10),
83
+ "-$.1" => MonetaryValue.new(-10),
84
+ "-$1.23" => MonetaryValue.new(-123),
85
+ "-$1.20" => MonetaryValue.new(-120),
86
+ "-$1.2" => MonetaryValue.new(-120),
87
+
88
+ "$-0.00" => MonetaryValue.new(0),
89
+ "$-0.0" => MonetaryValue.new(0),
90
+ "$-.00" => MonetaryValue.new(0),
91
+ "$-.0" => MonetaryValue.new(0),
92
+ "$-0.12" => MonetaryValue.new(-12),
93
+ "$-0.10" => MonetaryValue.new(-10),
94
+ "$-0.1" => MonetaryValue.new(-10),
95
+ "$-.12" => MonetaryValue.new(-12),
96
+ "$-.10" => MonetaryValue.new(-10),
97
+ "$-.1" => MonetaryValue.new(-10),
98
+ "$-1.23" => MonetaryValue.new(-123),
99
+ "$-1.20" => MonetaryValue.new(-120),
100
+ "$-1.2" => MonetaryValue.new(-120),
101
+
102
+ }
103
+
104
+ valid_strings.each do |valid_string, result|
105
+ begin
106
+ assert_equal result, MonetaryValue.parse(valid_string), valid_string
107
+ rescue ArgumentError
108
+ flunk "#{valid_string} should be able to be parsed"
109
+ end
110
+ end
111
+
112
+ invalid_strings = [
113
+ "",
114
+ "$",
115
+ ".",
116
+ "$.",
117
+
118
+ "-",
119
+ "-$",
120
+ "$-",
121
+ "-.",
122
+ "-$.",
123
+ "$-.",
124
+
125
+ "$1.234",
126
+ "a1.23",
127
+ "1a.23",
128
+ "1. 23",
129
+ "1 .23",
130
+ "1,23",
131
+ "1.023",
132
+ "0x1",
133
+
134
+ "0.",
135
+ "1.",
136
+ "$0.",
137
+ "$-1.",
138
+ "-$1.",
139
+ ]
140
+
141
+ invalid_strings.each do |invalid_string|
142
+ assert_raise ArgumentError do
143
+ MonetaryValue.parse(invalid_string)
144
+ end
145
+ end
146
+
147
+ end
148
+
149
+ end
@@ -0,0 +1,3 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'active_support'
metadata ADDED
@@ -0,0 +1,89 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: monetary_value
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease:
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 0
10
+ version: 1.0.0
11
+ platform: ruby
12
+ authors:
13
+ - Ian James
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-06-06 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
+ description: Provides the MonetaryValue and Currency classes.
35
+ email:
36
+ - ian@opencuro.com
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files: []
42
+
43
+ files:
44
+ - .gitignore
45
+ - Gemfile
46
+ - README.rdoc
47
+ - Rakefile
48
+ - lib/currency.rb
49
+ - lib/monetary_value.rb
50
+ - lib/monetary_value/version.rb
51
+ - monetary_value.gemspec
52
+ - test/currency_test.rb
53
+ - test/monetary_value_test.rb
54
+ - test/test_helper.rb
55
+ homepage: https://opencuro.com
56
+ licenses: []
57
+
58
+ post_install_message:
59
+ rdoc_options: []
60
+
61
+ require_paths:
62
+ - lib
63
+ required_ruby_version: !ruby/object:Gem::Requirement
64
+ none: false
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ hash: 3
69
+ segments:
70
+ - 0
71
+ version: "0"
72
+ required_rubygems_version: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ hash: 3
78
+ segments:
79
+ - 0
80
+ version: "0"
81
+ requirements: []
82
+
83
+ rubyforge_project:
84
+ rubygems_version: 1.8.10
85
+ signing_key:
86
+ specification_version: 3
87
+ summary: Library for working with monetary values
88
+ test_files: []
89
+